[
  {
    "path": ".codecov.yml",
    "content": "coverage:\n  parsers:\n    javascript:\n      enable_partials: yes\n"
  },
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[Makefile]\nindent_style = tab\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "open_collective: koajs\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: \"/\"\n    schedule:\n      interval: daily\n    open-pull-requests-limit: 5\n    versioning-strategy: increase-if-necessary\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "name: Node.js CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [18.x, 20.x, 22.x]\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v4\n      with:\n        node-version: ${{ matrix.node-version }}\n    - run: npm ci\n    - run: npm run lint\n    - run: npm run test:coverage\n    - name: Upload coverage to Codecov\n      uses: codecov/codecov-action@v5\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "content": "name: NPM Publish\n\n# Trigger when tags matching semver format are pushed, or manually via workflow_dispatch.\n# Manual triggers allow selecting a specific tag to publish (e.g. tags from the v2.x branch).\n#\n# Patterns match common semver formats:\n#   - v1.0.0 (standard)\n#   - v1.0.0-alpha (pre-release)\n#   - v1.0.0-beta.1 (pre-release with number)\n#\n# Note: GitHub Actions uses glob patterns (not full regex), which limits\n# complex semver matching. These patterns cover most npm publishing scenarios.\n# For complex dotted pre-releases (v1.0.0-alpha.beta.1), use simpler formats\n# like v1.0.0-alphabeta1 or create the workflow manually.\n\"on\":\n  push:\n    tags:\n      - 'v[0-9]+.[0-9]+.[0-9]+'\n      - 'v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z0-9]+'\n      - 'v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z0-9]+.[0-9]+'\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: 'Git tag to checkout and publish (e.g. v2.15.4)'\n        required: true\n        type: string\n\n# Permissions for NPM trusted publishing with provenance\npermissions:\n  contents: read\n  id-token: write\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5\n        with:\n          ref: ${{ inputs.tag || github.ref }}\n      \n      - name: Setup Node.js\n        uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 #v6\n        with:\n          node-version: 22\n\n      - name: Install npm@latest\n        run: npm install -g npm@latest\n      \n      - name: Install dependencies\n        run: npm ci\n      \n      - name: Publish to NPM\n        run: npm publish\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ntest.js\ncoverage\nnpm-debug.log\n.idea\n*.iml\ndist\n"
  },
  {
    "path": ".mailmap",
    "content": "Michał Gołębiowski-Owczarek <m.goleb@gmail.com>\n"
  },
  {
    "path": "AUTHORS",
    "content": "小菜 <xtx1130@gmail.com>\nAaron Heckmann <aaron.heckmann+github@gmail.com>\nAdam L <skyros@gmail.com>\nAdam Lau <skyros@gmail.com>\nAesop Wolf <aesopwolf@users.noreply.github.com>\nAlexeyKhristov <AlexeyKhristov@users.noreply.github.com>\nAlexsey <agat00@gmail.com>\nAmit Portnoy <amit.portnoy@gmail.com>\nAnton Harniakou <anton.harniakou@gmail.com>\nArjun <arjun453@gmail.com>\nAsiel Leal <lealceldeiro@gmail.com>\nAvindra Goolcharan <aavindraa@gmail.com>\nBartol Karuza <bartol.k@gmail.com>\nBen Reinhart <breinhart@groupon.com>\nBernie Stern <bernzs@gmail.com>\nBryan Bess <squarejaw@bsbess.com>\nC.T. Lin <chentsulin@gmail.com>\nChiahao Lin <purepennons@users.noreply.github.com>\nChris Tarquini <chris@ilsken.com>\nChristoffer Hallas <hallas@users.noreply.github.com>\nClark Du <clark.duxin@gmail.com>\nDarren Cauthon <darren@cauthon.com>\nDebjeet Biswas <debjeet@vxtindia.com>\nDmitry Mazuro <dmitry.mazuro@icloud.com>\nDouglas Christopher Wilson <doug@somethingdoug.com>\nEivind Fjeldstad <eivind.fjeldstad@gmail.com>\nEquim <sayaka@ekyu.moe>\nFangdun Cai <fundon@users.noreply.github.com>\nFelix Becker <felix.b@outlook.com>\nFilip Skokan <panva.ip@gmail.com>\nFrancisco Presencia <franciscop@users.noreply.github.com>\nGao Sheng <gaosheng08@meituan.com>\nGeorge Chung <Gerhut@GMail.com>\nGilles De Mey <gilles.de.mey@gmail.com>\nGrand <sungg12138@163.com>\nGuilherme Pacheco <guilherme.f.pacheco@hotmail.com>\nHanHor Wu <hanhor.wu@gmail.com>\nHartley Melamed <hartley@melamed.biz>\nHrvoje Šimić <hrvoje@twobucks.co>\nHugh Kennedy <hughskennedy@gmail.com>\nIan Storm Taylor <ian@ianstormtaylor.com>\nIlkka Oksanen <iao@iki.fi>\nIvan Kleshnin <ivan@paqmind.com>\nIvan Lyons <iliyang.cn@gmail.com>\nJacob Bass <jacob@jacobbass.net>\nJamesWang <likegun94@gmail.com>\nJan Buschtöns <buschtoens@gmail.com>\nJan Carlo Viray <virayjancarlo@yahoo.com>\nJason Macgowan <jason.macgowan@icloud.com>\nJed Schmidt <where@jed.is>\nJeff Moore <jeff@procata.com>\nJesus Rodriguez <foxandxss@gmail.com>\nJesús Rodríguez Rodríguez <Foxandxss@gmail.com>\nJingwei \"John\" Liu <liujingwei@gmail.com>\nJohan Bergström <bugs@bergstroem.nu>\nJonas Zhang <106856363@qq.com>\nJonathan Ong <jonathanrichardong@gmail.com>\nJonathan Ong <me@jongleberry.com>\nJoseph Lin <josephlin55555@gmail.com>\nJulian Gruber <julian@juliangruber.com>\nKareem Kwong <kareem.kwong@gmail.com>\nKarl Böhlmark <karl.bohlmark@gmail.com>\nKenneth Ormandy <kenneth@chloi.io>\nKim Joar Bekkelund <kjbekkelund@gmail.com>\nKwyn Alice Meagher <kwyn.meagher@gmail.com>\nKyle Suss <susskyle@gmail.com>\nLee Bousfield <ljbousfield@gmail.com>\nLouis DeScioli <louis.descioli@gmail.com>\nLuke Bousfield <math.master.champion@gmail.com>\nMalcolm <noinkling@users.noreply.github.com>\nMarceli.no <me@marceli.no>\nMars Wong <marswong618@gmail.com>\nMartin Iwanowski <martin@iwanowski.se>\nMartin Iwanowski <me@fl0w.io>\nMartin fl0w Iwanowski <martin@iwanowski.se>\nMatheus Azzi <matheuslazzi@gmail.com>\nMathieu Gallé-Tessonneau <mathieu.galletessonneau@gmail.com>\nMatthew Chase Whittemore <matthew@socialtables.com>\nMatthew King <mking@users.noreply.github.com>\nMatthew Mueller <mattmuelle@gmail.com>\nMengdi Gao <gaomdev@gmail.com>\nMichaël Zasso <mic.besace@gmail.com>\nMichał Gołębiowski-Owczarek <m.goleb@gmail.com>\nNathan Rajlich <nathan@tootallnate.net>\nNew Now Nohow <empty@cqdr.es>\nNick McCurdy <nick@nickmccurdy.com>\nNicolae Vartolomei <nvartolomei@gmail.com>\nPatrickJS <github@gdi2290.com>\nPaul Anderson <thesamuraipanda@gmail.com>\nPedro Pablo Aste Kompen <wachunei@gmail.com>\nPeeyush Kushwaha <peeyush.p97@gmail.com>\nPhillip Alexander <git@phillipalexander.io>\nPlasmaPower <ljbousfield@gmail.com>\nPrayag Verma <prayag.verma@gmail.com>\nQiming zhao <chemzqm@gmail.com>\nRemek Ambroziak <remek.ambroziak@gmail.com>\nRiceball LEE <snowyu.lee@gmail.com>\nRichard Marmorstein <twitchard@users.noreply.github.com>\nRico Sta. Cruz <rstacruz@users.noreply.github.com>\nRobert Sköld <robert@publicclass.se>\nRobin Pokorný <me@robinpokorny.com>\nRuben Bridgewater <ruben@bridgewater.de>\nRui Marinho <rpm@seegno.com>\nRui Marinho <ruipmarinho@gmail.com>\nRyunosuke SATO <tricknotes.rs@gmail.com>\nSaad Quadri <saad@saadq.com>\nSantiago Sotomayor <sansoto2003@yahoo.com.ar>\nSergei Osipov <hcz@users.noreply.github.com>\nShaun Warman <shaunwarman1@gmail.com>\nShawn Cheung <958033967@qq.com>\nShawn Sit <xueqingxiao@gmail.com>\nSlobodan Stojanovic <slobodan@cloudhorizon.com>\nSonny Piers <sonny@fastmail.net>\nSterling Williams <sterlingw@qualtrics.com>\nStéphane Bisinger <stephane.bisinger@protonmail.com>\nTJ Holowaychuk <tj@apex.sh>\nTJ Holowaychuk <tj@vision-media.ca>\nTaehwan, No <taehwanno.dev@gmail.com>\nTejas Manohar <me@tejas.io>\nTeoman Soygul <teo@soygul.com>\nThiago Lagden <lagden@gmail.com>\nTiago Ribeiro <tlr@seegno.com>\nTim Schaub <tim.schaub@gmail.com>\nTodor Stoychev <pretodor@gmail.com>\nTomas Ruud <tomasruud@users.noreply.github.com>\nTravis Jeffery <tj@travisjeffery.com>\nUsman Hussain <usmandap@gmail.com>\nVeselin Todorov <veselin@veselin.bg>\nWang Dàpéng <wonderfuly@gmail.com>\nXavier Damman <xdamman@gmail.com>\nXiang Gao <geekplux@qq.com>\nYanick Rochon <yanick.rochon@gmail.com>\nYazhong Liu <l900422@vip.qq.com>\nYazhong Liu <yorkiefixer@gmail.com>\nYiyu He <dead-horse@users.noreply.github.com>\nYiyu He <dead_horse@qq.com>\nYoshua Wuyts <yoshuawuyts@gmail.com>\nYu Qi <iyuq@outlook.com>\nYu Qi <njuyuqi@gmail.com>\nZack Tanner <zacktanner@gmail.com>\nalsotang <alsotang@gmail.com>\nbananaappletw <bananaappletw@gmail.com>\nbhanuc <bhanuc@iitk.ac.in>\nblaz <blaz@menems.net>\nbroucz <broucapierre@gmail.com>\nd3v <cr1s@users.noreply.github.com>\ndead-horse <dead_horse@qq.com>\ndead_horse <dead_horse@qq.com>\ndesigngrill <anshul@designgrill.com>\nfengmk2 <fengmk2@gmail.com>\nfengmk2 <m@fengmk2.com>\nfrank <frankxin93@hotmail.com>\nfundon <cfddream@gmail.com>\ngyson <eilian.yunsong@gmail.com>\nhaoxin <coderhaoxin@outlook.com>\nhaoxin <haoxins@icloud.com>\niamchenxin <iamchenxin@gmail.com>\ninitial-wu <initial-wu@outlook.com>\njeromew <jerome.wagner@m4x.org>\njoehecn <leanbrown@live.cn>\njongleberry <jonathanong@users.noreply.github.com>\njongleberry <me@jongleberry.com>\nllambda <xxgsoftware@gmail.com>\nmako-taco <jake.y.scott@gmail.com>\nmdemo <mds@xue.bi>\nnicoder <nicolas.dermine@gmail.com>\nnswbmw <gxqzk@126.com>\npana <pana.wang@outlook.com>\nqingming <358242939@qq.com>\nsong <xiongsongsong@outlook.com>\nsuperchink <superchink@gmail.com>\ntmilewski <tmilewski@gmail.com>\nyoshuawuyts <i@yoshuawuyts.com>\nyosssi <yoshida.keiji.84@gmail.com>\nzensh <admin@zensh.com>\nziyunfei <446240525@qq.com>\n石发磊 <sshsfl@yeah.net>\n"
  },
  {
    "path": "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\nadvances\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": "History.md",
    "content": "\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/koajs/koa/releases> in combination with [np](https://www.npmjs.com/package/np) for publishing releases and their changelogs.\n\n---\n\n3.0.0-alpha.3 / 2025-02-11\n==================\n\n**fixes**\n- Avoid redos on host and protocol getter\n\n3.0.0-alpha.2 / 2024-11-04\n==================\n\n**breaking changes**\n- Update `http-errors` to `v2.0.0` [#1486](https://github.com/koajs/koa/pull/1486)\n  - `ctx.throw` now requires a format of `ctx.throw(status, error, properties)`. See: https://www.npmjs.com/package/http-errors\n- Remove `res.redirect('back')`, add `back()` method to `ctx` [#1115](https://github.com/koajs/koa/pull/1115)\n- Replace node querystring with `URLSearchParams` [#1828](https://github.com/koajs/koa/pull/1828)\n- Remove obsolete `createAsyncCtxStorageMiddleware` [#1817](https://github.com/koajs/koa/pull/1817)\n\n**features**\n- Add support for web WHATWG [#1830](https://github.com/koajs/koa/pull/1830)\n\n**updates**\n- Update `cookies` to `~0.9.1` [#1846](https://github.com/koajs/koa/pull/1846)\n- Update `statuses` to `^2.0.1`\n- Update `supertest` to `^7.0.0` [#1841](https://github.com/koajs/koa/pull/1841)\n\n**fixes**\n- Fix `exports.defaults` in `package.json` [#1630](https://github.com/koajs/koa/pull/1630)\n- Fix leaky handles in tests [#1838](https://github.com/koajs/koa/pull/1838)\n- Fix body null checks [#1814](https://github.com/koajs/koa/pull/1814)\n- Fix reformatting redirect URLs [#1805](https://github.com/koajs/koa/pull/1805) [#1804](https://github.com/koajs/koa/pull/1804)\n- Fix passing `ctx` in error handler [#1758](https://github.com/koajs/koa/pull/1758)\n\n**migrations**\n- Migrate from `jest` to the native node test runner [#1845](https://github.com/koajs/koa/pull/1845)\n\n3.0.0-alpha.1 / 2023-04-12\n==================\n\n**fixes**\n  * [[`e98b8d1`](http://github.com/koajs/koa/commit/e98b8d1918376dc2957aa62906bf5893bef66c4c)] - fix: can not get currentContext in error handler (#1758) (Gxkl <<gxkl203@gmail.com>>)\n\n3.0.0-alpha.0 / 2023-01-02\n==================\n\n## Breaking Changes\n\n- Supports node@12+ only.\n- Removes generator deprecation messages.\n  Generators are no longer supported.\n  Koa no longer asserts if generators are used.\n- Set `content-length: 0` if body is explicitly set to `null` @ognjenjevremovic #1528\n\n## Features\n\n- Use asyncLocalStorage to get current context from app, e.g.: `const ctx = app.currentContext`.\n\n## Fixes\n\n- fix: Do not response Content-Length if Transfer-Encoding is defined #1562 @charlyzeng\n- fix: Set body to `null` if `ctx.type = json` and `ctx.body = null` #1059 @likegun\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": "LICENSE",
    "content": "(The MIT License)\n\nCopyright (c) 2019 Koa 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": "Readme.md",
    "content": "<img src=\"/docs/logo.png\" alt=\"Koa middleware framework for nodejs\"/>\n\n  [![gitter][gitter-image]][gitter-url]\n  [![NPM version][npm-image]][npm-url]\n  [![build status][github-action-image]][github-action-url]\n  [![Test coverage][coveralls-image]][coveralls-url]\n  [![OpenCollective Backers][backers-image]](#backers)\n  [![OpenCollective Sponsors][sponsors-image]](#sponsors)\n  [![PR's Welcome][pr-welcoming-image]][pr-welcoming-url]\n\n  Expressive 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\n  Only methods that are common to nearly all HTTP servers are integrated directly into Koa's small ~570 SLOC codebase. This\n  includes things like content negotiation, normalization of node inconsistencies, redirection, and a few others.\n\n  Koa is not bundled with any middleware.\n\n## Installation\n\nKoa requires __node v18.0.0__ or higher for ES2015 and async function support.\n\n```sh\nnpm install koa\n```\n\n## Hello Koa\n\n```js\nconst Koa = require('koa');\nconst app = new Koa();\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 - [Guide](docs/guide.md) - Go straight to the docs.\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 (node v7.6+)\n\n```js\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```js\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### Koa v1.x Middleware Signature\n\nThe middleware signature changed between v1.x and v2.x.  The older signature is deprecated.\n\n**Old signature middleware support has been removed in v3**\n\nPlease see the [Migration Guide from v2.x to v3.x](docs/migration-v2-to-v3.md) for information on upgrading from v2.x to v3.x, and the [Migration Guide from v1.x to v2.x](docs/migration-v1-to-v2.md) for information on upgrading from v1.x to v2.x.\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```js\napp.use(async (ctx, next) => { await next(); });\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```js\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```js\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```\n$ npm 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 [dead_horse](mailto:heyiyu.deadhorse@gmail.com), [jonathanong](mailto:me@jongleberry.com), and [niftylettuce](mailto:niftylettuce@gmail.com) to disclose.\n\n## Authors\n\nSee [AUTHORS](AUTHORS).\n\n## Community\n\n - [KoaJS Slack Group](https://join.slack.com/t/koa-js/shared_invite/zt-5pjgthmb-1JeKDbByqqcARtlPbtf~vQ)\n - [Badgeboard](https://koajs.github.io/badgeboard) and list of official modules\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 - [Reddit Community](https://www.reddit.com/r/koajs)\n - [Mailing list](https://groups.google.com/forum/#!forum/koajs)\n - [中文文档 v1.x](https://github.com/guo-yu/koa-guide)\n - [中文文档 v2.x](https://github.com/demopark/koa-docs-Zh-CN)\n - __[#koajs]__ on freenode\n\n## Backers\n\nSupport us with a monthly donation and help us continue our activities.\n\n<a href=\"https://opencollective.com/koajs/backer/0/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/1/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/2/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/3/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/4/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/5/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/6/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/7/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/8/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/9/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/9/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/10/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/10/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/11/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/11/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/12/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/12/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/13/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/13/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/14/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/14/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/15/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/15/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/16/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/16/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/17/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/17/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/18/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/18/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/19/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/19/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/20/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/20/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/21/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/21/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/22/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/22/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/23/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/23/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/24/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/24/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/25/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/25/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/26/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/26/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/27/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/27/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/28/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/28/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/backer/29/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/backer/29/avatar.svg\"></a>\n\n\n## Sponsors\n\nBecome a sponsor and get your logo on our README on Github with a link to your site.\n\n<a href=\"https://opencollective.com/koajs/sponsor/0/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/1/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/2/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/3/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/4/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/5/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/6/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/7/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/8/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/9/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/9/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/10/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/10/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/11/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/11/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/12/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/12/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/13/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/13/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/14/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/14/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/15/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/15/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/16/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/16/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/17/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/17/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/18/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/18/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/19/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/19/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/20/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/20/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/21/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/21/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/22/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/22/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/23/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/23/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/24/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/24/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/25/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/25/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/26/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/26/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/27/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/27/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/28/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/28/avatar.svg\"></a>\n<a href=\"https://opencollective.com/koajs/sponsor/29/website\" target=\"_blank\"><img src=\"https://opencollective.com/koajs/sponsor/29/avatar.svg\"></a>\n\n# License\n\n  [MIT](https://github.com/koajs/koa/blob/master/LICENSE)\n\n[npm-image]: https://img.shields.io/npm/v/koa.svg?style=flat-square\n[npm-url]: https://www.npmjs.com/package/koa\n[github-action-image]: https://github.com/koajs/koa/actions/workflows/node.js.yml/badge.svg\n[github-action-url]: https://github.com/koajs/koa/actions/workflows/node.js.yml\n[coveralls-image]: https://img.shields.io/codecov/c/github/koajs/koa.svg?style=flat-square\n[coveralls-url]: https://codecov.io/github/koajs/koa?branch=master\n[backers-image]: https://opencollective.com/koajs/backers/badge.svg?style=flat-square\n[sponsors-image]: https://opencollective.com/koajs/sponsors/badge.svg?style=flat-square\n[gitter-image]: https://img.shields.io/gitter/room/koajs/koa.svg?style=flat-square\n[gitter-url]: https://gitter.im/koajs/koa?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge\n[#koajs]: https://webchat.freenode.net/?channels=#koajs\n[pr-welcoming-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square\n[pr-welcoming-url]: https://github.com/koajs/koa/pull/new\n"
  },
  {
    "path": "__tests__/.eslintrc.yml",
    "content": "env:\n  jest: true\n\nrules:\n  space-before-blocks: [2, {functions: never, keywords: always}]\n  no-unused-expressions: 0\n  node/no-deprecated-api: 'warn'\n  quote-props: 'warn'\n  no-prototype-builtins: 'warn'\n  array-bracket-spacing: 'warn'\n  object-curly-spacing: 'warn'\n  dot-notation: 'warn'\n"
  },
  {
    "path": "__tests__/application/compose.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\n\ndescribe('app.compose', () => {\n  it('should work with default compose ', async () => {\n    const app = new Koa()\n    const calls = []\n\n    app.use((ctx, next) => {\n      calls.push(1)\n      return next().then(() => {\n        calls.push(4)\n      })\n    })\n\n    app.use((ctx, next) => {\n      calls.push(2)\n      return next().then(() => {\n        calls.push(3)\n      })\n    })\n\n    await request(app.callback())\n      .get('/')\n      .expect(404)\n\n    assert.deepStrictEqual(calls, [1, 2, 3, 4])\n  })\n\n  it('should work with configurable compose', async () => {\n    const calls = []\n    let count = 0\n    const app = new Koa({\n      compose (fns) {\n        return async (ctx) => {\n          const dispatch = async () => {\n            count++\n            const fn = fns.shift()\n            fn && fn(ctx, dispatch)\n          }\n          dispatch()\n        }\n      }\n    })\n\n    app.use((ctx, next) => {\n      calls.push(1)\n      next()\n      calls.push(4)\n    })\n    app.use((ctx, next) => {\n      calls.push(2)\n      next()\n      calls.push(3)\n    })\n\n    await request(app.callback())\n      .get('/')\n\n    assert.deepStrictEqual(calls, [1, 2, 3, 4])\n    assert.equal(count, 3)\n  })\n})\n"
  },
  {
    "path": "__tests__/application/context.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\n\ndescribe('app.context', () => {\n  const app1 = new Koa()\n  app1.context.msg = 'hello'\n  const app2 = new Koa()\n\n  it('should merge properties', () => {\n    app1.use((ctx, next) => {\n      assert.strictEqual(ctx.msg, 'hello')\n      ctx.status = 204\n    })\n\n    return request(app1.callback())\n      .get('/')\n      .expect(204)\n  })\n\n  it('should not affect the original prototype', () => {\n    app2.use((ctx, next) => {\n      assert.strictEqual(ctx.msg, undefined)\n      ctx.status = 204\n    })\n\n    return request(app2.callback())\n      .get('/')\n      .expect(204)\n  })\n})\n"
  },
  {
    "path": "__tests__/application/currentContext.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\nconst { AsyncLocalStorage } = require('async_hooks')\n\ndescribe('app.currentContext', () => {\n  it('should get currentContext return context when asyncLocalStorage enable', async () => {\n    const app = new Koa({ asyncLocalStorage: true })\n\n    app.use(async ctx => {\n      assert(ctx === app.currentContext)\n      await new Promise(resolve => {\n        setTimeout(() => {\n          assert(ctx === app.currentContext)\n          resolve()\n        }, 1)\n      })\n      await new Promise(resolve => {\n        assert(ctx === app.currentContext)\n        setImmediate(() => {\n          assert(ctx === app.currentContext)\n          resolve()\n        })\n      })\n      assert(ctx === app.currentContext)\n      app.currentContext.body = 'ok'\n    })\n\n    const requestServer = async () => {\n      assert(app.currentContext === undefined)\n      await request(app.callback()).get('/').expect('ok')\n      assert(app.currentContext === undefined)\n    }\n\n    await Promise.all([\n      requestServer(),\n      requestServer(),\n      requestServer(),\n      requestServer(),\n      requestServer()\n    ])\n  })\n\n  it('should get currentContext return undefined when asyncLocalStorage disable', async () => {\n    const app = new Koa()\n\n    app.use(async ctx => {\n      assert(app.currentContext === undefined)\n      ctx.body = 'ok'\n    })\n\n    await request(app.callback()).get('/').expect('ok')\n  })\n\n  it('should get currentContext return context in error handler when asyncLocalStorage enable', async () => {\n    const app = new Koa({ asyncLocalStorage: true })\n\n    app.use(async () => {\n      throw new Error('error message')\n    })\n\n    const handleError = new Promise((resolve, reject) => {\n      app.on('error', (err, ctx) => {\n        try {\n          assert.strictEqual(err.message, 'error message')\n          assert.strictEqual(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  it('should get currentContext return undefined in error handler when asyncLocalStorage disable', async () => {\n    const app = new Koa()\n\n    app.use(async () => {\n      throw new Error('error message')\n    })\n\n    const handleError = new Promise((resolve, reject) => {\n      app.on('error', (err, ctx) => {\n        try {\n          assert.strictEqual(err.message, 'error message')\n          assert.strictEqual(app.currentContext, undefined)\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  it('should support a custom asyncLocalStorage', async () => {\n    const asyncLocalStorage = new AsyncLocalStorage()\n    const app = new Koa({ asyncLocalStorage })\n    assert(app.currentContext === undefined)\n    app.use(async ctx => {\n      assert(ctx === app.currentContext)\n      assert(asyncLocalStorage.getStore() === ctx)\n      ctx.body = 'ok'\n    })\n    await request(app.callback()).get('/').expect('ok')\n    assert(app.currentContext === undefined)\n  })\n})\n"
  },
  {
    "path": "__tests__/application/index.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst { once } = require('node:events')\nconst Koa = require('../..')\n\ndescribe('app', () => {\n  it('should handle socket errors', async () => {\n    const app = new Koa()\n    let errorCaught = false\n\n    app.use((ctx) => {\n      ctx.socket.destroy(new Error('boom'))\n    })\n\n    app.on('error', err => {\n      assert.strictEqual(err.message, 'boom')\n      errorCaught = true\n    })\n\n    const server = app.listen()\n\n    try {\n      const req = require('http').get({\n        port: server.address().port\n      })\n      req.on('error', () => {})\n\n      const [err] = await once(app, 'error')\n      assert.strictEqual(err.message, 'boom')\n      assert.strictEqual(errorCaught, true)\n    } finally {\n      await server.close()\n    }\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 = ['customkey']\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 set compose from the constructor', () => {\n    const compose = () => (ctx) => {}\n    const app = new Koa.default({ compose }) // eslint-disable-line new-cap\n    assert.strictEqual(app.compose, compose)\n  })\n\n  it('should have a static property exporting `HttpError` from http-errors library', () => {\n    const CreateError = require('http-errors')\n\n    assert.notEqual(Koa.HttpError, undefined)\n    assert.deepStrictEqual(Koa.HttpError, CreateError.HttpError)\n    assert.throws(() => { throw new CreateError(500, 'test error') }, Koa.HttpError)\n  })\n})\n"
  },
  {
    "path": "__tests__/application/inspect.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst util = require('util')\nconst Koa = require('../..')\n\nprocess.env.NODE_ENV = 'test'\nconst app = new Koa()\n\ndescribe('app.inspect()', () => {\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(\n      { subdomainOffset: 2, proxy: false, env: 'test' },\n      app.inspect()\n    )\n  })\n})\n"
  },
  {
    "path": "__tests__/application/onerror.test.js",
    "content": "'use strict'\n\nconst { describe, it, mock } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\n\ndescribe('app.onerror(err)', () => {\n  it('should throw an error if a non-error is given', () => {\n    const app = new Koa()\n\n    assert.throws(() => {\n      app.onerror('foo')\n    }, TypeError, 'non-error thrown: foo')\n  })\n\n  it('should accept errors coming from other scopes', () => {\n    const ExternError = require('vm').runInNewContext('Error')\n\n    const app = new Koa()\n    const error = Object.assign(new ExternError('boom'), {\n      status: 418,\n      expose: true\n    })\n\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()\n\n    err.status = 404\n\n    const spy = mock.method(console, 'error', () => {})\n    app.onerror(err)\n    assert.strictEqual(spy.mock.calls.length, 0)\n    spy.mock.restore()\n  })\n\n  it('should do nothing if .silent', () => {\n    const app = new Koa()\n    app.silent = true\n    const err = new Error()\n\n    const spy = mock.method(console, 'error', () => {})\n    app.onerror(err)\n    assert.strictEqual(spy.mock.calls.length, 0)\n    spy.mock.restore()\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()\n    err.stack = 'Foo'\n\n    const spy = mock.method(console, 'error', () => {})\n    app.onerror(err)\n    assert.notStrictEqual(spy.mock.calls.length, 0)\n    spy.mock.restore()\n  })\n})\n"
  },
  {
    "path": "__tests__/application/request.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\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, next) => {\n      assert.strictEqual(ctx.request.message, 'hello')\n      ctx.status = 204\n    })\n\n    return request(app1.callback())\n      .get('/')\n      .expect(204)\n  })\n\n  it('should not affect the original prototype', () => {\n    app2.use((ctx, next) => {\n      assert.strictEqual(ctx.request.message, undefined)\n      ctx.status = 204\n    })\n\n    return request(app2.callback())\n      .get('/')\n      .expect(204)\n  })\n})\n"
  },
  {
    "path": "__tests__/application/respond.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst statuses = require('statuses')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\nconst fs = require('fs')\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      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .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      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('lol')\n        .expect(res => {\n          assert(!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      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .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 res = await request(app.callback())\n        .get('/')\n        .expect(200)\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(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 res = await request(app.callback())\n        .head('/')\n        .expect(200)\n\n      assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8')\n      assert.strictEqual(res.headers['content-length'], '5')\n      assert(!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 res = await request(app.callback())\n        .head('/')\n        .expect(200)\n\n      assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')\n      assert.strictEqual(res.headers['content-length'], '17')\n      assert(!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 res = await request(app.callback())\n        .head('/')\n        .expect(200)\n\n      assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8')\n      assert.strictEqual(res.headers['content-length'], '11')\n      assert(!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 res = await request(app.callback())\n        .head('/')\n        .expect(200)\n\n      assert.strictEqual(res.headers['content-type'], 'application/octet-stream')\n      assert.strictEqual(res.headers['content-length'], '11')\n      assert(!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 res = await request(app.callback())\n        .head('/')\n        .expect(200)\n\n      assert.strictEqual(~~res.header['content-length'], length)\n      assert(!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(ctx => {\n\n      })\n\n      return request(app.callback())\n        .head('/')\n        .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      return request(app.callback())\n        .head('/')\n        .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      return request(app.callback())\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      return request(app.callback())\n        .get('/')\n        .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, next) => {\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 => { throw err })\n\n      return request(app.callback())\n        .get('/')\n        .expect(200)\n    })\n\n    it('should send the right body', () => {\n      const app = new Koa()\n\n      app.use((ctx, next) => {\n        const res = ctx.res\n        ctx.status = 200\n        res.setHeader('Content-Type', 'text/html')\n        res.write('Hello')\n        return new Promise(resolve => {\n          setTimeout(() => {\n            res.end('Goodbye')\n            resolve()\n          }, 0)\n        })\n      })\n\n      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .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        return request(app.callback())\n          .get('/')\n          .expect(400)\n          .expect('Content-Length', '11')\n          .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 res = await request(app.callback())\n          .get('/')\n          .expect(204)\n          .expect('')\n\n        assert.strictEqual(Object.prototype.hasOwnProperty.call(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 res = await request(app.callback())\n          .get('/')\n          .expect(205)\n          .expect('')\n\n        assert.strictEqual(Object.prototype.hasOwnProperty.call(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 res = await request(app.callback())\n          .get('/')\n          .expect(304)\n          .expect('')\n\n        assert.strictEqual(Object.prototype.hasOwnProperty.call(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 res = await request(app.callback())\n          .get('/')\n          .expect(700)\n          .expect('custom status')\n\n        assert.strictEqual(res.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 res = await request(app.callback())\n          .get('/')\n          .expect(200)\n          .expect('ok')\n\n        assert.strictEqual(res.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        return request(app.callback())\n          .get('/')\n          .expect(701)\n          .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 res = await request(app.callback())\n        .get('/')\n        .expect(204)\n        .expect('')\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(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 res = await request(app.callback())\n        .get('/')\n        .expect(204)\n        .expect('')\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(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 res = await request(app.callback())\n        .get('/')\n        .expect(205)\n        .expect('')\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(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 res = await request(app.callback())\n        .get('/')\n        .expect(304)\n        .expect('')\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)\n    })\n  })\n\n  describe('when .body is undefined', () => {\n    it('should respond 204 by default', async () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        ctx.body = undefined\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(204)\n        .expect('')\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)\n    })\n\n    it('should respond 204 with status=200', async () => {\n      const app = new Koa()\n      app.use(ctx => {\n        ctx.status = 200\n        ctx.body = undefined\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(204)\n        .expect('')\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(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      return request(app.callback())\n        .get('/')\n        .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      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect(Buffer.from('Hello'))\n    })\n  })\n\n  describe('when .body is a Blob', () => {\n    it('should respond', async () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        ctx.body = new Blob(['Hello'])\n      })\n\n      const expectedBlob = new Blob(['Hello'])\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(200)\n\n      assert.deepStrictEqual(res.body, Buffer.from(await expectedBlob.arrayBuffer()))\n    })\n\n    it('should keep Blob headers', async () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        ctx.body = new Blob(['hello world'])\n      })\n\n      return request(app.callback())\n        .head('/')\n        .expect(200)\n        .expect('content-type', 'application/octet-stream')\n        .expect('content-length', '11')\n    })\n  })\n\n  describe('when .body is a ReadableStream', () => {\n    it('should respond', async () => {\n      const app = new Koa()\n\n      app.use(async ctx => {\n        ctx.body = new ReadableStream()\n      })\n\n      return request(app.callback())\n        .head('/')\n        .expect(200)\n        .expect('content-type', 'application/octet-stream')\n    })\n\n    it('should respond hello', async () => {\n      const app = new Koa()\n\n      app.use(async ctx => {\n        const blob = new Blob(['hello'])\n        ctx.body = blob.stream()\n      })\n\n      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('content-type', 'application/octet-stream')\n        .expect(Buffer.from('hello'))\n    })\n\n    it('should handle ReadableStream with chunks', async () => {\n      const app = new Koa()\n\n      app.use(async ctx => {\n        const stream = new ReadableStream({\n          start (controller) {\n            controller.enqueue(new TextEncoder().encode('Hello '))\n            controller.enqueue(new TextEncoder().encode('World'))\n            controller.close()\n          }\n        })\n        ctx.body = stream\n      })\n\n      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('content-type', 'application/octet-stream')\n        .expect(Buffer.from('Hello World'))\n    })\n\n    it('should handle ReadableStream with custom headers', async () => {\n      const app = new Koa()\n\n      app.use(async ctx => {\n        ctx.type = 'text/plain'\n        ctx.body = new ReadableStream({\n          start (controller) {\n            controller.enqueue(new TextEncoder().encode('test content'))\n            controller.close()\n          }\n        })\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('content-type', 'text/plain; charset=utf-8')\n\n      assert.strictEqual(res.text, 'test content')\n    })\n  })\n\n  describe('when .body is a Response', () => {\n    it('should keep Response headers', () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        ctx.body = new Response(null, { status: 201, statusText: 'OK', headers: { 'Content-Type': 'text/plain' } })\n      })\n\n      return request(app.callback())\n        .head('/')\n        .expect(201)\n        .expect('content-type', 'text/plain')\n        .expect('content-length', '2')\n    })\n\n    it('should default to octet-stream', () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        ctx.body = new Response(null, { status: 200, statusText: 'OK' })\n      })\n\n      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('content-type', 'application/octet-stream')\n        .expect(Buffer.from([]))\n    })\n\n    it('should respond with body content', async () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        ctx.body = new Response('Hello World', { status: 200, headers: { 'Content-Type': 'text/plain' } })\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('content-type', 'text/plain')\n\n      assert.strictEqual(res.text, 'Hello World')\n    })\n\n    it('should handle Response from fetch() with JSON', async () => {\n      const app = new Koa()\n\n      app.use(async ctx => {\n        const jsonData = JSON.stringify({ message: 'Hello from fetch', timestamp: Date.now() })\n        const response = new Response(jsonData, {\n          status: 200,\n          headers: {\n            'Content-Type': 'application/json',\n            'X-Custom-Header': 'custom-value'\n          }\n        })\n        ctx.body = response\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('content-type', 'application/json')\n\n      const body = JSON.parse(res.text)\n      assert.strictEqual(body.message, 'Hello from fetch')\n      assert(body.timestamp)\n    })\n\n    it('should handle Response from fetch() with streaming body', async () => {\n      const app = new Koa()\n\n      app.use(async ctx => {\n        const stream = new ReadableStream({\n          start (controller) {\n            controller.enqueue(new TextEncoder().encode('Streaming '))\n            controller.enqueue(new TextEncoder().encode('response '))\n            controller.enqueue(new TextEncoder().encode('from fetch'))\n            controller.close()\n          }\n        })\n\n        const response = new Response(stream, {\n          status: 200,\n          headers: {\n            'Content-Type': 'text/plain'\n          }\n        })\n        ctx.body = response\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('content-type', 'text/plain')\n\n      assert.strictEqual(res.text, 'Streaming response from fetch')\n    })\n\n    it('should handle Response from fetch() with Blob body', async () => {\n      const app = new Koa()\n\n      app.use(async ctx => {\n        const blob = new Blob(['Hello from Blob'], { type: 'text/plain' })\n        const response = new Response(blob, {\n          status: 200,\n          headers: {\n            'Content-Type': 'text/plain'\n          }\n        })\n        ctx.body = response\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(200)\n        .expect('content-type', 'text/plain')\n\n      assert.strictEqual(res.text, 'Hello from Blob')\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 res = await request(app.callback())\n        .get('/')\n        .expect('Content-Type', 'application/json; charset=utf-8')\n\n      const pkg = require('../../package')\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), false)\n      assert.deepStrictEqual(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 res = await request(app.callback())\n        .get('/')\n        .expect('Content-Type', 'application/json; charset=utf-8')\n\n      const pkg = require('../../package')\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), false)\n      assert.deepStrictEqual(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 res = await request(app.callback())\n        .get('/')\n        .expect('Content-Type', 'application/json; charset=utf-8')\n\n      const pkg = require('../../package')\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), true)\n      assert.deepStrictEqual(res.body, pkg)\n    })\n\n    it('should keep content-length if overwritten with the same stream',\n      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 res = await request(app.callback())\n          .get('/')\n          .expect('Content-Type', 'application/json; charset=utf-8')\n\n        const pkg = require('../../package')\n        assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), true)\n        assert.deepStrictEqual(res.body, pkg)\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      return request(app.callback())\n        .get('/')\n        .expect(204)\n    })\n  })\n\n  describe('when using pipeline for streams', () => {\n    it('should handle stream errors when error listener exists', async () => {\n      const app = new Koa()\n      const PassThrough = require('stream').PassThrough\n\n      let errorCaught = false\n      app.once('error', err => {\n        assert(err.message === 'stream error')\n        errorCaught = true\n      })\n\n      app.use(ctx => {\n        const stream = new PassThrough()\n        ctx.body = stream\n\n        setImmediate(() => {\n          stream.emit('error', new Error('stream error'))\n        })\n      })\n\n      await request(app.callback())\n        .get('/')\n        .catch(() => {})\n\n      await new Promise(resolve => setTimeout(resolve, 50))\n      assert(errorCaught, 'Error should have been caught')\n    })\n\n    it('should not crash when stream errors and no error listener exists', async () => {\n      const app = new Koa()\n      const PassThrough = require('stream').PassThrough\n\n      app.use(ctx => {\n        const stream = new PassThrough()\n        ctx.body = stream\n\n        setImmediate(() => {\n          stream.emit('error', new Error('stream error'))\n        })\n      })\n\n      await request(app.callback())\n        .get('/')\n        .catch(() => {})\n\n      await new Promise(resolve => setTimeout(resolve, 50))\n    })\n\n    it('should handle ReadableStream errors when error listener exists', async () => {\n      const app = new Koa()\n\n      let errorCaught = false\n      app.once('error', err => {\n        assert(err.message === 'readable stream error')\n        errorCaught = true\n      })\n\n      app.use(ctx => {\n        const readable = new ReadableStream({\n          start (controller) {\n            controller.enqueue(new TextEncoder().encode('data'))\n            controller.error(new Error('readable stream error'))\n          }\n        })\n        ctx.body = readable\n      })\n\n      await request(app.callback())\n        .get('/')\n        .catch(() => {})\n\n      await new Promise(resolve => setTimeout(resolve, 50))\n      assert(errorCaught, 'Error should have been caught')\n    })\n\n    it('should cleanup streams on client abort', async () => {\n      const app = new Koa()\n      const PassThrough = require('stream').PassThrough\n      const http = require('http')\n\n      let streamDestroyed = false\n\n      app.use(ctx => {\n        const stream = new PassThrough()\n        stream.on('close', () => {\n          streamDestroyed = true\n        })\n        ctx.body = stream\n\n        setImmediate(() => {\n          stream.write('some data')\n        })\n      })\n\n      const server = app.listen()\n\n      await new Promise((resolve) => {\n        const req = http.request({\n          port: server.address().port,\n          path: '/'\n        })\n\n        req.on('response', (res) => {\n          res.on('data', () => {\n            req.destroy()\n            setTimeout(() => {\n              server.close()\n              resolve()\n            }, 50)\n          })\n        })\n\n        req.end()\n      })\n\n      assert(streamDestroyed, 'Stream should be destroyed on client abort')\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      return request(app.callback())\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        return request(app.callback())\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      let errorCaught = false\n\n      app.on('error', err => {\n        assert.strictEqual(err.message, 'test error')\n        errorCaught = true\n      })\n\n      app.use(ctx => {\n        throw new Error('test error')\n      })\n\n      await request(app.callback())\n        .get('/')\n        .expect(500)\n\n      assert.strictEqual(errorCaught, true)\n    })\n\n    describe('with an .expose property', () => {\n      it('should expose the message', () => {\n        const app = new Koa()\n\n        app.use(ctx => {\n          const err = new Error('sorry!')\n          err.status = 403\n          err.expose = true\n          throw err\n        })\n\n        return request(app.callback())\n          .get('/')\n          .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(ctx => {\n          const err = new Error('s3 explodes')\n          err.status = 403\n          throw err\n        })\n\n        return request(app.callback())\n          .get('/')\n          .expect(403, 'Forbidden')\n      })\n    })\n\n    it('should respond with 500', () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        throw new Error('boom!')\n      })\n\n      return request(app.callback())\n        .get('/')\n        .expect(500, 'Internal Server Error')\n    })\n\n    it('should be catchable', () => {\n      const app = new Koa()\n\n      app.use((ctx, next) => {\n        return next().then(() => {\n          ctx.body = 'Hello'\n        }).catch(() => {\n          ctx.body = 'Got error'\n        })\n      })\n\n      app.use((ctx, next) => {\n        throw new Error('boom!')\n      })\n\n      return request(app.callback())\n        .get('/')\n        .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      return request(app.callback())\n        .get('/')\n        .expect(200)\n        .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 res = await request(app.callback())\n        .get('/')\n        .expect(204)\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(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      return request(app.callback())\n        .get('/')\n        .expect(404)\n        .expect('')\n        .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 res = await request(app.callback())\n        .get('/')\n        .expect(401)\n        .expect('')\n        .expect({})\n\n      assert.equal(Object.prototype.hasOwnProperty.call(res.headers, 'transfer-encoding'), false)\n      assert.equal(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)\n      assert.equal(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), true)\n    })\n\n    it('should return content-length equal to 0', async () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        ctx.body = null\n        ctx.status = 401\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(401)\n        .expect('')\n        .expect({})\n\n      assert.equal(res.headers['content-length'], '0')\n    })\n    it('should not overwrite the content-length', async () => {\n      const app = new Koa()\n\n      app.use(ctx => {\n        ctx.body = null\n        ctx.length = 10\n        ctx.status = 404\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(404)\n        .expect('')\n        .expect({})\n\n      assert.equal(res.headers['content-length'], '0')\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/application/response.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\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  const app6 = new Koa()\n  const app7 = new Koa()\n\n  it('should merge properties', () => {\n    app1.use((ctx, next) => {\n      assert.strictEqual(ctx.response.msg, 'hello')\n      ctx.status = 204\n    })\n\n    return request(app1.callback())\n      .get('/')\n      .expect(204)\n  })\n\n  it('should not affect the original prototype', () => {\n    app2.use((ctx, next) => {\n      assert.strictEqual(ctx.response.msg, undefined)\n      ctx.status = 204\n    })\n\n    return request(app2.callback())\n      .get('/')\n      .expect(204)\n  })\n\n  it('should not include status message in body for http2', async () => {\n    app3.use((ctx, next) => {\n      ctx.req.httpVersionMajor = 2\n      ctx.status = 404\n    })\n    const response = await request(app3.callback())\n      .get('/')\n      .expect(404)\n    assert.strictEqual(response.text, '404')\n  })\n\n  it('should set ._explicitNullBody correctly', async () => {\n    app4.use((ctx, next) => {\n      ctx.body = null\n      assert.strictEqual(ctx.response._explicitNullBody, true)\n    })\n\n    return request(app4.callback())\n      .get('/')\n      .expect(204)\n  })\n\n  it('should not set ._explicitNullBody incorrectly', async () => {\n    app5.use((ctx, next) => {\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.callback())\n      .get('/')\n      .expect(204)\n  })\n\n  it('should add Content-Length when Transfer-Encoding is not defined', () => {\n    app6.use((ctx, next) => {\n      ctx.body = 'hello world'\n    })\n\n    return request(app6.callback())\n      .get('/')\n      .expect('Content-Length', '11')\n      .expect(200)\n  })\n\n  it('should not add Content-Length when Transfer-Encoding is defined', () => {\n    app7.use((ctx, next) => {\n      ctx.set('Transfer-Encoding', 'chunked')\n      ctx.body = 'hello world'\n      assert.strictEqual(ctx.response.get('Content-Length'), undefined)\n    })\n\n    return request(app7.callback())\n      .get('/')\n      .expect('Transfer-Encoding', 'chunked')\n      .expect(200)\n  })\n})\n"
  },
  {
    "path": "__tests__/application/toJSON.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\n\ndescribe('app.toJSON()', () => {\n  it('should work', () => {\n    const app = new Koa({ env: 'test' })\n    const obj = app.toJSON()\n\n    assert.deepStrictEqual({\n      subdomainOffset: 2,\n      proxy: false,\n      env: 'test'\n    }, obj)\n  })\n})\n"
  },
  {
    "path": "__tests__/application/use.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\n\ndescribe('app.use(fn)', () => {\n  it('should compose middleware', async () => {\n    const app = new Koa()\n    const calls = []\n\n    app.use((ctx, next) => {\n      calls.push(1)\n      return next().then(() => {\n        calls.push(6)\n      })\n    })\n\n    app.use((ctx, next) => {\n      calls.push(2)\n      return next().then(() => {\n        calls.push(5)\n      })\n    })\n\n    app.use((ctx, next) => {\n      calls.push(3)\n      return next().then(() => {\n        calls.push(4)\n      })\n    })\n\n    await request(app.callback())\n      .get('/')\n      .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 = []\n\n    app.use((ctx, next) => {\n      calls.push(1)\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      return next().then(() => {\n        calls.push(4)\n      })\n    })\n\n    await request(app.callback())\n      .get('/')\n      .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', () => {\n    const app = new Koa()\n\n    app.use(ctx => ctx.throw(404, 'Not Found'))\n\n    return request(app.callback()).get('/').expect(404)\n  })\n\n  it('should throw error for non-function', () => {\n    const app = new Koa();\n\n    [null, undefined, 0, false, 'not a function'].forEach(v => {\n      assert.throws(() => app.use(v), /middleware must be a function!/)\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/context/assert.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst assert = require('node:assert/strict')\n\ndescribe('ctx.assert(value, status)', () => {\n  it('should throw an error', () => {\n    const ctx = context()\n\n    let assertionRan = false\n    try {\n      ctx.assert(false, 404, 'custom message')\n      throw new Error('should not reach here')\n    } catch (err) {\n      assertionRan = true\n      assert.strictEqual(err.status, 404)\n      assert.strictEqual(err.message, 'custom message')\n      assert.strictEqual(err.expose, true)\n    }\n    assert(assertionRan)\n  })\n})\n"
  },
  {
    "path": "__tests__/context/cookies.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('supertest')\nconst Koa = require('../..')\n\ndescribe('ctx.cookies', () => {\n  describe('ctx.cookies.set()', () => {\n    it('should set an unsigned cookie', async () => {\n      const app = new Koa()\n\n      app.use((ctx, next) => {\n        ctx.cookies.set('name', 'jon')\n        ctx.status = 204\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(204)\n\n      const cookie = res.headers['set-cookie'].some(cookie => /^name=/.test(cookie))\n      assert.strictEqual(cookie, true)\n    })\n\n    describe('with .signed', () => {\n      describe('when no .keys are set', () => {\n        it('should error', () => {\n          const app = new Koa()\n\n          app.use((ctx, next) => {\n            try {\n              ctx.cookies.set('foo', 'bar', { signed: true })\n            } catch (err) {\n              ctx.body = err.message\n            }\n          })\n\n          return request(app.callback())\n            .get('/')\n            .expect('.keys required for signed cookies')\n        })\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, next) => {\n          ctx.cookies.set('name', 'jon', { signed: true })\n          ctx.status = 204\n        })\n\n        const res = await request(app.callback())\n          .get('/')\n          .expect(204)\n\n        const cookies = res.headers['set-cookie']\n\n        assert.strictEqual(cookies.some(cookie => /^name=/.test(cookie)), true)\n        assert.strictEqual(cookies.some(cookie => /(,|^)name\\.sig=/.test(cookie)), 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 res = await request(app.callback())\n          .get('/')\n          .set('x-forwarded-proto', 'https') // mock secure\n          .expect(204)\n\n        const cookies = res.headers['set-cookie']\n        assert.strictEqual(cookies.some(cookie => /^name=/.test(cookie)), true)\n        assert.strictEqual(cookies.some(cookie => /(,|^)name\\.sig=/.test(cookie)), true)\n        assert.strictEqual(cookies.every(cookie => /secure/.test(cookie)), 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, next) => {\n        ctx.cookies = {\n          set (key, value) {\n            ctx.set(key, value)\n          }\n        }\n        ctx.cookies.set('name', 'jon')\n        ctx.status = 204\n      })\n\n      await request(app.callback())\n        .get('/')\n        .expect('name', 'jon')\n        .expect(204)\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/context/inspect.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst prototype = require('../../lib/context')\nconst assert = require('node:assert/strict')\nconst util = require('util')\nconst context = require('../../test-helpers/context')\n\ndescribe('ctx.inspect()', () => {\n  it('should return a json representation', () => {\n    const ctx = context()\n    const toJSON = ctx.toJSON(ctx)\n\n    assert.deepStrictEqual(toJSON, ctx.inspect())\n    assert.deepStrictEqual(util.inspect(toJSON), util.inspect(ctx))\n  })\n\n  // console.log(require.cache) will call prototype.inspect()\n  it('should not crash when called on the prototype', () => {\n    assert.deepStrictEqual(prototype, prototype.inspect())\n    assert.deepStrictEqual(util.inspect(prototype.inspect()), util.inspect(prototype))\n  })\n})\n"
  },
  {
    "path": "__tests__/context/onerror.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('supertest')\nconst Koa = require('../..')\nconst context = require('../../test-helpers/context')\n\ndescribe('ctx.onerror(err)', () => {\n  it('should respond', () => {\n    const app = new Koa()\n\n    app.use((ctx, next) => {\n      ctx.body = 'something else'\n\n      ctx.throw(418, 'boom')\n    })\n\n    return request(app.callback())\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, next) => {\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 res = await request(app.callback())\n      .get('/')\n      .expect(418)\n      .expect('Content-Type', 'text/plain; charset=utf-8')\n      .expect('Content-Length', '4')\n\n    assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'vary'), false)\n    assert.strictEqual(Object.prototype.hasOwnProperty.call(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, next) => {\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 res = await request(app.callback())\n      .get('/')\n      .expect(418)\n      .expect('Content-Type', 'text/plain; charset=utf-8')\n      .expect('X-New-Header', 'Value')\n\n    assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'vary'), false)\n    assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'x-csrf-token'), false)\n  })\n\n  it('should ignore error after headerSent', async () => {\n    const app = new Koa()\n\n    app.on('error', (err, { res }) => {\n      assert.strictEqual(err.message, 'mock error')\n      assert.strictEqual(err.headerSent, true)\n      res.end()\n    })\n\n    app.use(async ctx => {\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())\n      .get('/')\n      .expect('X-Foo', 'Bar')\n      .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, next) => {\n      ctx.body = 'something else'\n      const err = new Error('Not found')\n      err.statusCode = 404\n      throw err\n    })\n\n    return request(app.callback())\n      .get('/')\n      .expect(404)\n      .expect('Content-Type', 'text/plain; charset=utf-8')\n      .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, next) => {\n          ctx.body = 'something else'\n          const err = new Error('some error')\n          err.statusCode = 'notnumber'\n          throw err\n        })\n\n        return request(app.callback())\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', () => {\n        const app = new Koa()\n\n        app.use((ctx, next) => {\n          ctx.body = 'something else'\n          const err = new Error('some error')\n          err.status = 'notnumber'\n          throw err\n        })\n\n        return request(app.callback())\n          .get('/')\n          .expect(500)\n          .expect('Content-Type', 'text/plain; charset=utf-8')\n          .expect('Internal Server Error')\n      })\n    })\n    describe('not http status code', () => {\n      it('should respond 500', () => {\n        const app = new Koa()\n\n        app.use((ctx, next) => {\n          ctx.body = 'something else'\n          const err = new Error('some error')\n          err.status = 9999\n          throw err\n        })\n\n        return request(app.callback())\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 = require('vm').runInNewContext('Error')\n\n      const app = new Koa()\n      const error = Object.assign(new ExternError('boom'), {\n        status: 418,\n        expose: true\n      })\n      app.use((ctx, next) => {\n        throw error\n      })\n\n      const gotRightErrorPromise = new Promise((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(app.callback())\n        .get('/')\n        .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((ctx, next) => {\n        throw 'string error' // eslint-disable-line no-throw-literal\n      })\n\n      return request(app.callback())\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.emit = () => {}\n      ctx.res = {\n        getHeaderNames: () => ['content-type', 'content-length'],\n        removeHeader: () => removed++,\n        end: () => {},\n        emit: () => {}\n      }\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      app.on('error', err => {\n        let assertionRan = false\n        assert.strictEqual(err.message, 'non-error thrown: {\"key\":\"value\"}')\n        assertionRan = true\n        assert(assertionRan, 'assertion was not executed')\n      })\n\n      app.use(async ctx => {\n        throw { key: 'value' } // eslint-disable-line no-throw-literal\n      })\n\n      await request(app.callback())\n        .get('/')\n        .expect(500)\n        .expect('Internal Server Error')\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/context/state.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\n\ndescribe('ctx.state', () => {\n  it('should provide a ctx.state namespace', () => {\n    const app = new Koa()\n\n    app.use(ctx => {\n      assert.deepStrictEqual(ctx.state, {})\n    })\n\n    return request(app.callback())\n      .get('/')\n      .expect(404)\n  })\n})\n"
  },
  {
    "path": "__tests__/context/throw.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst assert = require('node:assert/strict')\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 (err) {\n      assert.strictEqual(err.status, 500)\n      assert.strictEqual(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 (err) {\n      assert.strictEqual(err.status, 500)\n      assert.strictEqual(err.message, 'test')\n      assert.strictEqual(err.expose, false)\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 (err) {\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, msg)', () => {\n  it('should throw an error', () => {\n    const ctx = context()\n\n    try {\n      ctx.throw(400, 'name required')\n    } catch (err) {\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)', () => {\n  it('should throw an error', () => {\n    const ctx = context()\n\n    try {\n      ctx.throw(400)\n    } catch (err) {\n      assert.strictEqual(err.message, 'Bad Request')\n      assert.strictEqual(err.status, 400)\n      assert.strictEqual(err.expose, true)\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.status = -1\n        ctx.throw(err)\n      } catch (err) {\n        assert.strictEqual(err.message, 'some error')\n        assert.strictEqual(err.expose, false)\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 (err) {\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 (err) {\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 (err) {\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 (err) {\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 (err) {\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": "__tests__/context/toJSON.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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    const obj = JSON.parse(JSON.stringify(ctx))\n    const req = obj.request\n    const res = obj.response\n\n    assert.deepStrictEqual({\n      method: 'POST',\n      url: '/items',\n      header: {\n        'content-type': 'text/plain'\n      }\n    }, req)\n\n    assert.deepStrictEqual({\n      status: 200,\n      message: 'OK',\n      header: {\n        'content-type': 'text/html; charset=utf-8',\n        'content-length': 10\n      }\n    }, res)\n  })\n})\n"
  },
  {
    "path": "__tests__/lib/search-params.test.js",
    "content": "const { describe, it } = require('node:test')\nconst sp = require('../../lib/search-params')\nconst assert = require('node:assert/strict')\n\ndescribe('search-params', () => {\n  describe('stringify', () => {\n    it('Should stringify a simple object', () => {\n      assert.deepStrictEqual(sp.stringify({ a: 1, b: 'b' }), 'a=1&b=b')\n    })\n\n    it('Should stringify an object with an array', () => {\n      assert.deepStrictEqual(sp.stringify({ a: [1, 2] }), 'a=1&a=2')\n    })\n\n    it('Should stringify an object with an array with a single value', () => {\n      assert.deepStrictEqual(sp.stringify({ a: [1] }), 'a=1')\n    })\n\n    it('Stringify an object with an array with a single empty value', () => {\n      assert.deepStrictEqual(sp.stringify({ a: [''] }), 'a=')\n    })\n\n    it('Should not stringify an object with a nested object', () => {\n      assert.deepStrictEqual(sp.stringify({ a: { b: 1 } }), 'a=')\n    })\n  })\n\n  describe('parse', () => {\n    it('Should parse a simple query string', () => {\n      assert.deepStrictEqual(sp.parse('a=1&b=2'), { a: '1', b: '2' })\n    })\n\n    it('Should parse a query string with same key and multiple values', () => {\n      assert.deepEqual(sp.parse('a=1&a=2'), { a: ['1', '2'] })\n    })\n\n    it('Should parse a query string with an array with a single empty value', () => {\n      assert.deepStrictEqual(sp.parse('a='), { a: '' })\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/load-with-esm.test.js",
    "content": "const { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\ndescribe('Load with esm', () => {\n  it('should default export koa', async () => {\n    const exported = await import('koa')\n    const required = require('../')\n    assert.strictEqual(exported.default, required)\n  })\n\n  it('should match exports own property names', async () => {\n    const exported = new Set(Object.getOwnPropertyNames(await import('koa')))\n    const required = new Set(Object.getOwnPropertyNames(require('../')))\n\n    // Remove constructor properties + default export.\n    for (const k of ['prototype', 'length', 'name']) {\n      required.delete(k)\n    }\n\n    // Commented out to \"fix\" CommonJS, ESM, bundling issue.\n    // @see https://github.com/koajs/koa/issues/1513\n    // exported.delete('default');\n\n    assert.strictEqual(exported.size, required.size)\n    assert.strictEqual([...exported].every(property => required.has(property)), true)\n  })\n\n  it('CommonJS exports default property', async () => {\n    const required = require('../')\n    assert.strictEqual(Object.prototype.hasOwnProperty.call(required, 'default'), true)\n  })\n\n  it('CommonJS exports default property referencing self', async () => {\n    const required = require('../')\n    assert.strictEqual(required.default, required)\n  })\n})\n"
  },
  {
    "path": "__tests__/request/accept.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst Accept = require('accepts')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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(ctx.accept instanceof Accept)\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 = context.request()\n    request.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain'\n    ctx.accept = Accept(request.req)\n    assert.deepStrictEqual(ctx.accepts(), ['text/html', 'text/plain', 'image/jpeg', 'application/*'])\n  })\n})\n"
  },
  {
    "path": "__tests__/request/accepts.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/request/acceptsCharsets.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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(), ['utf-8', 'utf-7', 'iso-8859-1'])\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          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        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      assert.strictEqual(ctx.acceptsCharsets(['utf-7', 'utf-8']), 'utf-8')\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/request/acceptsEncodings.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/request/acceptsLanguages.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/request/charset.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').request\nconst assert = require('node:assert/strict')\n\ndescribe('req.charset', () => {\n  describe('with no content-type present', () => {\n    it('should return \"\"', () => {\n      const req = request()\n      assert(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(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      assert.strictEqual(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.strictEqual(req.charset, '')\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/request/fresh.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/request/get.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/request/header.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('../../test-helpers/context').request\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 = { 'X-Custom-Headerfield': 'Its one header, with headerfields' }\n    assert.deepStrictEqual(req.header, req.req.headers)\n  })\n})\n"
  },
  {
    "path": "__tests__/request/headers.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('../../test-helpers/context').request\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 = { 'X-Custom-Headerfield': 'Its one header, with headerfields' }\n    assert.deepStrictEqual(req.headers, req.req.headers)\n  })\n})\n"
  },
  {
    "path": "__tests__/request/host.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').request\nconst assert = require('node:assert/strict')\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  })\n\n  describe('with Host header containing @', () => {\n    it('should correctly parse host from userinfo@host format', () => {\n      const req = request()\n      req.header.host = 'evil.com:fake@legitimate.com'\n      assert.strictEqual(req.host, 'legitimate.com')\n    })\n\n    it('should correctly parse host from user@host format', () => {\n      const req = request()\n      req.header.host = 'user@example.com'\n      assert.strictEqual(req.host, 'example.com')\n    })\n\n    it('should correctly parse host with port from userinfo@host:port format', () => {\n      const req = request()\n      req.header.host = 'user:pass@example.com:8080'\n      assert.strictEqual(req.host, 'example.com:8080')\n    })\n\n    it('should correctly parse @ in X-Forwarded-Host when proxy is trusted', () => {\n      const req = request()\n      req.app.proxy = true\n      req.header['x-forwarded-host'] = 'evil.com:fake@legitimate.com'\n      req.header.host = 'foo.com'\n      assert.strictEqual(req.host, 'legitimate.com')\n    })\n\n    it('should correctly parse @ in :authority on HTTP/2', () => {\n      const req = request({\n        httpVersionMajor: 2,\n        httpVersion: '2.0'\n      })\n      req.header[':authority'] = 'evil.com:fake@legitimate.com'\n      req.header.host = 'foo.com'\n      assert.strictEqual(req.host, 'legitimate.com')\n    })\n\n    it('should return empty string for invalid host with @', () => {\n      const req = request()\n      req.header.host = 'user@'\n      assert.strictEqual(req.host, '')\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/request/hostname.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').request\nconst assert = require('node:assert/strict')\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  describe('with Host header containing @', () => {\n    it('should correctly parse hostname from userinfo@host format', () => {\n      const req = request()\n      req.header.host = 'evil.com:fake@legitimate.com'\n      assert.strictEqual(req.hostname, 'legitimate.com')\n    })\n\n    it('should correctly parse hostname from user@host format', () => {\n      const req = request()\n      req.header.host = 'user@example.com'\n      assert.strictEqual(req.hostname, 'example.com')\n    })\n\n    it('should correctly parse hostname with port from userinfo@host:port format', () => {\n      const req = request()\n      req.header.host = 'user:pass@example.com:8080'\n      assert.strictEqual(req.hostname, 'example.com')\n    })\n\n    it('should correctly parse @ in X-Forwarded-Host when proxy is trusted', () => {\n      const req = request()\n      req.app.proxy = true\n      req.header['x-forwarded-host'] = 'evil.com:fake@legitimate.com'\n      req.header.host = 'foo.com'\n      assert.strictEqual(req.hostname, 'legitimate.com')\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/request/href.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Stream = require('stream')\nconst request = require('supertest')\nconst Koa = require('../../')\nconst context = require('../../test-helpers/context')\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('should work with `GET http://example.com/foo`', async () => {\n    const app = new Koa()\n    app.use(ctx => {\n      ctx.body = ctx.href\n    })\n\n    const res = await request(app.callback())\n      .get('/foo')\n      .set('Host', 'example.com')\n      .expect(200)\n\n    assert.strictEqual(res.text, 'http://example.com/foo')\n  })\n})\n"
  },
  {
    "path": "__tests__/request/idempotent.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('../../test-helpers/context').request\n\ndescribe('ctx.idempotent', () => {\n  describe('when the request method is idempotent', () => {\n    it('should return true', () => {\n      ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'].forEach(check)\n      function check (method) {\n        const req = request()\n        req.method = method\n        assert.strictEqual(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.strictEqual(req.idempotent, false)\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/request/inspect.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').request\nconst assert = require('node:assert/strict')\nconst util = require('util')\n\ndescribe('req.inspect()', () => {\n  describe('with no request.req present', () => {\n    it('should return null', () => {\n      const req = request()\n      req.method = 'GET'\n      delete req.req\n      assert(undefined === req.inspect())\n      assert(util.inspect(req) === 'undefined')\n    })\n  })\n\n  it('should return a json representation', () => {\n    const req = request()\n    req.method = 'GET'\n    req.url = 'example.com'\n    req.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.deepStrictEqual(req.inspect(), expected)\n    assert.deepStrictEqual(util.inspect(req), util.inspect(expected))\n  })\n})\n"
  },
  {
    "path": "__tests__/request/ip.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Stream = require('stream')\nconst Koa = require('../..')\nconst Request = require('../../test-helpers/context').request\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 = { headers: {}, socket: new Stream.Duplex() }\n      app.proxy = true\n      req.headers['x-forwarded-for'] = '127.0.0.1'\n      req.socket.remoteAddress = '127.0.0.2'\n      const request = Request(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.remoteAddress = '127.0.0.2'\n      const request = Request(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        })\n        assert.strictEqual(Request({ socket }).ip, '')\n      })\n    })\n  })\n\n  it('should be lazy inited and cached', () => {\n    const req = { socket: new Stream.Duplex() }\n    req.socket.remoteAddress = '127.0.0.2'\n    const request = Request(req)\n    assert.strictEqual(request.ip, '127.0.0.2')\n    req.socket.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.remoteAddress = '127.0.0.2'\n    const request = Request(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": "__tests__/request/ips.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('../../test-helpers/context').request\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        const 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  })\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": "__tests__/request/is.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst assert = require('node:assert/strict')\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": "__tests__/request/length.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').request\nconst assert = require('node:assert/strict')\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": "__tests__/request/origin.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Stream = require('stream')\nconst context = require('../../test-helpers/context')\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        origin: 'http://example.com'\n      },\n      socket,\n      __proto__: Stream.Readable.prototype\n    }\n    const ctx = context(req)\n    assert.strictEqual(ctx.origin, 'http://example.com')\n\n    // change it also work\n    ctx.url = '/foo/users/1?next=/dashboard'\n    assert.strictEqual(ctx.origin, 'http://example.com')\n  })\n})\n"
  },
  {
    "path": "__tests__/request/path.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\nconst parseurl = require('parseurl')\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": "__tests__/request/protocol.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('../../test-helpers/context').request\n\ndescribe('req.protocol', () => {\n  describe('when encrypted', () => {\n    it('should return \"https\"', () => {\n      const req = request()\n      req.req.socket = { encrypted: true }\n      assert.strictEqual(req.protocol, 'https')\n    })\n  })\n\n  describe('when unencrypted', () => {\n    it('should return \"http\"', () => {\n      const req = request()\n      req.req.socket = {}\n      assert.strictEqual(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        req.req.socket = {}\n        req.header['x-forwarded-proto'] = 'https, http'\n        assert.strictEqual(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          req.req.socket = {}\n          req.header['x-forwarded-proto'] = ''\n          assert.strictEqual(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        req.req.socket = {}\n        req.header['x-forwarded-proto'] = 'https, http'\n        assert.strictEqual(req.protocol, 'http')\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/request/query.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\n\ndescribe('ctx.query', () => {\n  describe('when missing', () => {\n    it('should return an empty object', () => {\n      const ctx = context({ url: '/' })\n      assert(!Object.keys(ctx.query).length)\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    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": "__tests__/request/querystring.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\nconst parseurl = require('parseurl')\n\ndescribe('ctx.querystring', () => {\n  it('should return the querystring', () => {\n    const ctx = context({ url: '/store/shoes?page=2&color=blue' })\n    assert.strictEqual(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      ctx.request.req = null\n      assert.strictEqual(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.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')\n    assert.strictEqual(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.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')\n    assert.strictEqual(ctx.search, '?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.querystring = '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  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.strictEqual(url.path, '/login?foo=bar')\n  })\n})\n"
  },
  {
    "path": "__tests__/request/search.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/request/secure.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('../../test-helpers/context').request\n\ndescribe('req.secure', () => {\n  it('should return true when encrypted', () => {\n    const req = request()\n    req.req.socket = { encrypted: true }\n    assert.strictEqual(req.secure, true)\n  })\n})\n"
  },
  {
    "path": "__tests__/request/stale.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/request/subdomains.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('../../test-helpers/context').request\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": "__tests__/request/type.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').request\nconst assert = require('node:assert/strict')\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": "__tests__/request/whatwg-url.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').request\nconst assert = require('node:assert/strict')\n\ndescribe('req.URL', () => {\n  it('should not throw when host is void', () => {\n    // Accessing the URL should not throw.\n    request().URL // eslint-disable-line no-unused-expressions\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    req.URL // eslint-disable-line no-unused-expressions\n  })\n\n  it('should return empty object when invalid', () => {\n    const req = request()\n    req.header.host = 'invalid host'\n    assert.deepStrictEqual(req.URL, Object.create(null))\n  })\n})\n"
  },
  {
    "path": "__tests__/response/append.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/response/attachment.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\nconst request = require('supertest')\nconst Koa = require('../..')\n\ndescribe('ctx.attachment([filename])', () => {\n  describe('security: prevent Content-Type override (GHSA-c5vw-j4hf-j526)', () => {\n    it('should NOT override Content-Type when already set', () => {\n      const ctx = context()\n      ctx.response.set('Content-Type', 'application/octet-stream')\n      ctx.attachment('malicious.html')\n      assert.strictEqual(ctx.response.get('Content-Type'), 'application/octet-stream')\n      assert.strictEqual(ctx.response.header['content-disposition'], 'attachment; filename=\"malicious.html\"')\n    })\n\n    it('should preserve safe Content-Type for SVG files', () => {\n      const ctx = context()\n      ctx.response.set('Content-Type', 'application/octet-stream')\n      ctx.attachment('image.svg')\n      assert.strictEqual(ctx.response.get('Content-Type'), 'application/octet-stream')\n    })\n\n    it('should set Content-Type when not previously set', () => {\n      const ctx = context()\n      ctx.attachment('document.pdf')\n      assert.strictEqual(ctx.response.get('Content-Type'), 'application/pdf')\n    })\n  })\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.strictEqual(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.strictEqual(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 = '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.strictEqual(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, next) => {\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('content-disposition', '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        .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(() => { ctx.attachment('plans.pdf', { fallback: 42 }) },\n        /fallback.*string/)\n    })\n\n    it('should default to true', () => {\n      const ctx = context()\n      ctx.attachment('€ rates.pdf')\n      assert.strictEqual(ctx.response.header['content-disposition'],\n        'attachment; filename=\"? rates.pdf\"; filename*=UTF-8\\'\\'%E2%82%AC%20rates.pdf')\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.strictEqual(ctx.response.header['content-disposition'],\n          'attachment; filename*=UTF-8\\'\\'%C2%A3%20and%20%E2%82%AC%20rates.pdf')\n      })\n\n      it('should keep ISO-8859-1 filename', () => {\n        const ctx = context()\n        ctx.attachment('£ rates.pdf', { fallback: false })\n        assert.strictEqual(ctx.response.header['content-disposition'],\n          '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.strictEqual(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      it('should pass through ISO-8859-1 filename', () => {\n        const ctx = context()\n        ctx.attachment('£ rates.pdf', { fallback: true })\n        assert.strictEqual(ctx.response.header['content-disposition'],\n          '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(() => { 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', { fallback: '£ and EURO rates.pdf' })\n        assert.strictEqual(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      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.strictEqual(ctx.response.header['content-disposition'],\n          'attachment; filename=\"£ rates.pdf\"; filename*=UTF-8\\'\\'%22%C2%A3%20rates%22.pdf')\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.strictEqual(ctx.response.header['content-disposition'],\n          '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.strictEqual(ctx.response.header['content-disposition'],\n          'attachment; filename=\"EURO rates.pdf\"; filename*=UTF-8\\'\\'%E2%82%AC%20rates.pdf')\n      })\n\n      it('should do nothing without filename option', () => {\n        const ctx = context()\n        ctx.attachment(undefined, { fallback: 'plans.pdf' })\n        assert.strictEqual(ctx.response.header['content-disposition'],\n          '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.strictEqual(ctx.response.header['content-disposition'],\n        'attachment')\n    })\n\n    it('should require a string', () => {\n      const ctx = context()\n      assert.throws(() => { ctx.attachment(undefined, { type: 42 }) },\n        /invalid type/)\n    })\n\n    it('should require a valid type', () => {\n      const ctx = context()\n      assert.throws(() => { 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.strictEqual(ctx.response.header['content-disposition'],\n        '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.strictEqual(ctx.response.header['content-disposition'],\n        'inline; filename=\"plans.pdf\"')\n    })\n\n    it('should normalize type', () => {\n      const ctx = context()\n      ctx.attachment(undefined, { type: 'INLINE' })\n      assert.strictEqual(ctx.response.header['content-disposition'],\n        'inline')\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/response/back.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\n\ndescribe('ctx.back([alt])', () => {\n  it('should redirect to Referrer', () => {\n    const ctx = context({ url: '/', headers: { host: 'example.com' } })\n    ctx.req.headers.referrer = '/login'\n    ctx.back()\n    assert.equal(ctx.response.header.location, '/login')\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.back()\n    assert.equal(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.back()\n    assert.equal(ctx.response.header.location, '/')\n  })\n\n  it('should redirect to Referer to a relative path', () => {\n    const ctx = context({ url: '/', headers: { host: 'example.com' } })\n    ctx.req.headers.referer = '/login'\n    ctx.back()\n    assert.equal(ctx.response.header.location, '/login')\n  })\n\n  it('should redirect to Referer to a same origin url', () => {\n    const ctx = context({ url: '/', headers: { host: 'example.com', referer: 'https://example.com/login' } })\n    ctx.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.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.back()\n    assert.equal(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.back()\n    assert.equal(ctx.response.header.location, '/')\n\n    ctx.back('/home')\n    assert.equal(ctx.response.header.location, '/home')\n  })\n})\n"
  },
  {
    "path": "__tests__/response/body.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst response = require('../../test-helpers/context').response\nconst CustomStream = require('../../test-helpers/stream')\nconst assert = require('node:assert/strict')\nconst fs = require('fs')\nconst Stream = require('stream')\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 = '    <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 support custom stream', () => {\n      const res = response()\n      res.body = new CustomStream.Readable()\n      assert.strictEqual('application/octet-stream', res.header['content-type'])\n    })\n\n    it('should not add error handler to stream (handled by pipeline)', () => {\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'), 0)\n      res.body = body\n      assert.strictEqual(body.listenerCount('error'), 0)\n    })\n\n    it('should NOT cleanup original stream when replaced by new stream (to support wrapping middleware)', () => {\n      const res = response()\n      const stream1 = new Stream.PassThrough()\n      const stream2 = new Stream.PassThrough()\n\n      res.body = stream1\n      res.body = stream2\n\n      assert.strictEqual(stream1.destroyed, false)\n      assert.strictEqual(stream2.destroyed, false)\n    })\n\n    it('should cleanup original stream when replaced by null', () => {\n      const res = response()\n      const stream = new Stream.PassThrough()\n\n      res.body = stream\n      res.body = null\n\n      assert.strictEqual(stream.destroyed, true)\n    })\n\n    it('should not throw unhandled errors when replacing failing stream', async () => {\n      const res = response()\n\n      const stream1 = new Stream.Readable({\n        read () {\n        }\n      })\n\n      const stream2 = new Stream.PassThrough()\n\n      res.body = stream1\n      res.body = stream2\n\n      await new Promise((resolve) => {\n        process.nextTick(() => {\n          stream1.emit('error', new Error('stream1 error'))\n          setTimeout(resolve, 10)\n        })\n      })\n    })\n\n    it('should handle multiple sequential stream replacements', () => {\n      const res = response()\n      const stream1 = new Stream.PassThrough()\n      const stream2 = new Stream.PassThrough()\n      const stream3 = new Stream.PassThrough()\n\n      res.body = stream1\n      res.body = stream2\n      res.body = stream3\n\n      assert.strictEqual(stream1.destroyed, false)\n      assert.strictEqual(stream2.destroyed, false)\n      assert.strictEqual(stream3.destroyed, false)\n    })\n\n    it('should handle four sequential stream replacements', () => {\n      const res = response()\n      const stream1 = new Stream.PassThrough()\n      const stream2 = new Stream.PassThrough()\n      const stream3 = new Stream.PassThrough()\n      const stream4 = new Stream.PassThrough()\n\n      res.body = stream1\n      res.body = stream2\n      res.body = stream3\n      res.body = stream4\n\n      assert.strictEqual(stream1.destroyed, false)\n      assert.strictEqual(stream2.destroyed, false)\n      assert.strictEqual(stream3.destroyed, false)\n      assert.strictEqual(stream4.destroyed, false)\n    })\n\n    it('should cleanup stream when replaced by string', () => {\n      const res = response()\n      const stream = new Stream.PassThrough()\n\n      res.body = stream\n      res.body = 'hello'\n\n      assert.strictEqual(stream.destroyed, true)\n    })\n\n    it('should cleanup stream when replaced by buffer', () => {\n      const res = response()\n      const stream = new Stream.PassThrough()\n\n      res.body = stream\n      res.body = Buffer.from('hello')\n\n      assert.strictEqual(stream.destroyed, true)\n    })\n\n    it('should cleanup stream when replaced by object', () => {\n      const res = response()\n      const stream = new Stream.PassThrough()\n\n      res.body = stream\n      res.body = { foo: 'bar' }\n\n      assert.strictEqual(stream.destroyed, true)\n    })\n\n    it('should support wrapping stream middleware (like koa-compress)', async () => {\n      const res = response()\n      const sourceStream = new Stream.Readable({\n        read () {\n          this.push('hello world')\n          this.push(null)\n        }\n      })\n\n      res.body = sourceStream\n\n      const wrappedStream = new Stream.PassThrough()\n      sourceStream.pipe(wrappedStream)\n      res.body = wrappedStream\n\n      assert.strictEqual(sourceStream.destroyed, false)\n      assert.strictEqual(wrappedStream.destroyed, false)\n\n      const chunks = []\n      for await (const chunk of wrappedStream) {\n        chunks.push(chunk)\n      }\n      const result = Buffer.concat(chunks).toString()\n      assert.strictEqual(result, 'hello world')\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  describe('when a ReadableStream is given', () => {\n    it('should default to an octet stream', () => {\n      const res = response()\n      res.body = new ReadableStream()\n      assert.strictEqual('application/octet-stream', res.header['content-type'])\n    })\n  })\n\n  describe('when a Blob is given', () => {\n    it('should default to an octet stream', () => {\n      const res = response()\n      res.body = new Blob([new Uint8Array([1, 2, 3])], { type: 'application/octet-stream' })\n      assert.strictEqual('application/octet-stream', res.header['content-type'])\n    })\n\n    it('should set length', () => {\n      const res = response()\n      res.body = new Blob([new Uint8Array([1, 2, 3])], { type: 'application/octet-stream' })\n      assert.strictEqual(3, res.header['content-length'])\n    })\n  })\n\n  describe('when a response is given', () => {\n    it('should set the status', () => {\n      const res = response()\n      res.body = new Response(null, { status: 201 })\n      assert.strictEqual(201, res.status)\n    })\n\n    it('should set headers', () => {\n      const res = response()\n      res.body = new Response(null, { status: 200, headers: { 'x-fizz': 'buzz', 'x-foo': 'bar' } })\n      assert.strictEqual('buzz', res.header['x-fizz'])\n      assert.strictEqual('bar', res.header['x-foo'])\n    })\n\n    it('should redirect', () => {\n      const res = response()\n      res.body = Response.redirect('https://www.example.com/', 301)\n      assert.strictEqual(301, res.status)\n      assert.strictEqual('https://www.example.com/', res.header.location)\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/response/etag.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response = require('../../test-helpers/context').response\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": "__tests__/response/flushHeaders.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\nconst http = require('http')\nconst { once } = require('node:events')\n\ndescribe('ctx.flushHeaders()', () => {\n  it('should set headersSent', () => {\n    const app = new Koa()\n\n    app.use((ctx, next) => {\n      ctx.body = 'Body'\n      ctx.status = 200\n      ctx.flushHeaders()\n      assert.strictEqual(ctx.headerSent, true)\n      assert.strictEqual(ctx.res.headersSent, true)\n    })\n\n    return request(app.callback())\n      .get('/')\n      .expect(200)\n      .expect('Body')\n  })\n\n  it('should allow a response afterwards', () => {\n    const app = new Koa()\n\n    app.use((ctx, next) => {\n      ctx.status = 200\n      ctx.res.setHeader('Content-Type', 'text/plain')\n      ctx.flushHeaders()\n      ctx.body = 'Body'\n    })\n\n    return request(app.callback())\n      .get('/')\n      .expect(200)\n      .expect('Content-Type', 'text/plain')\n      .expect('Body')\n  })\n\n  it('should send the correct status code', () => {\n    const app = new Koa()\n\n    app.use((ctx, next) => {\n      ctx.status = 401\n      ctx.res.setHeader('Content-Type', 'text/plain')\n      ctx.flushHeaders()\n      ctx.body = 'Body'\n    })\n\n    return request(app.callback())\n      .get('/')\n      .expect(401)\n      .expect('Content-Type', 'text/plain')\n      .expect('Body')\n  })\n\n  it('should ignore set header after flushHeaders', async () => {\n    const app = new Koa()\n\n    app.use((ctx, next) => {\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 res = await request(app.callback())\n      .get('/')\n      .expect(401)\n      .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 PassThrough = require('stream').PassThrough\n    const app = new Koa()\n    let headersFlushed = false\n    let dataReceived = false\n\n    app.use(ctx => {\n      ctx.type = 'json'\n      ctx.status = 200\n      ctx.headers.Link = '</css/mycss.css>; as=style; rel=preload, <https://img.craftflair.com>; rel=preconnect; crossorigin'\n      const stream = ctx.body = new PassThrough()\n      ctx.flushHeaders()\n      headersFlushed = true\n      setTimeout(() => {\n        stream.end(JSON.stringify({ message: 'hello!' }))\n      }, 10)\n    })\n\n    const server = app.listen()\n    await once(server, 'listening')\n    const port = server.address().port\n\n    try {\n      const req = http.request({ port })\n      req.end()\n\n      const [res] = await once(req, 'response')\n      assert(headersFlushed, 'Headers should be flushed')\n\n      const dataPromise = new Promise(resolve => {\n        res.once('data', chunk => {\n          dataReceived = true\n          resolve(chunk)\n        })\n      })\n\n      // Wait for data with a timeout\n      const timeoutPromise = new Promise((resolve, reject) =>\n        setTimeout(() => reject(new Error('Timeout waiting for data')), 100)\n      )\n\n      await Promise.race([dataPromise, timeoutPromise])\n      res.destroy()\n      assert(dataReceived, 'Data should be received after headers')\n    } finally {\n      server.close()\n    }\n  })\n\n  it('should catch stream error', async () => {\n    const PassThrough = require('stream').PassThrough\n    const app = new Koa()\n    app.once('error', err => {\n      assert(err.message === 'mock error')\n    })\n\n    app.use(ctx => {\n      ctx.type = 'json'\n      ctx.status = 200\n      ctx.headers.Link = '</css/mycss.css>; as=style; rel=preload, <https://img.craftflair.com>; rel=preconnect; crossorigin'\n      ctx.length = 20\n      ctx.flushHeaders()\n      const stream = ctx.body = new PassThrough()\n\n      setTimeout(() => {\n        stream.emit('error', new Error('mock error'))\n        stream.end()\n      })\n    })\n\n    await request(app.callback()).get('/')\n      .then(() => {\n        throw new Error('should not successfully end')\n      })\n      .catch(err => {\n        assert(err.message === 'aborted')\n      })\n  })\n})\n"
  },
  {
    "path": "__tests__/response/get.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\n\ndescribe('ctx.get(name)', () => {\n  it('should get a field value, case insensitive', () => {\n    const ctx = context()\n    ctx.set('X-Foo', 'bar')\n    assert.strictEqual(ctx.response.get('x-FOO'), 'bar')\n  })\n\n  it('should have the same behavior as ctx.res.getHeader on undefined and null values', () => {\n    const ctx = context()\n    ctx.res.setHeader('X-Foo', undefined)\n    ctx.response.header['x-boo'] = null\n    assert.strictEqual(ctx.response.get('x-FOO'), ctx.res.getHeader('X-FOO'))\n    assert.strictEqual(ctx.response.get('x-bOO'), ctx.res.getHeader('X-BOO'))\n  })\n\n  it('should not convert header value type', () => {\n    const ctx = context()\n    ctx.res.setHeader('Foo-date', new Date())\n    ctx.response.header['foo-map'] = new Map()\n    ctx.res.setHeader('Foo-empty-string', '')\n    ctx.res.setHeader('Foo-number', 0)\n    ctx.res.setHeader('Foo-null', null)\n    ctx.res.setHeader('Foo-undefined', undefined)\n    assert.ok(ctx.response.get('foo-Date') instanceof Date)\n    assert.ok(ctx.response.get('foo-Map') instanceof Map)\n    assert.strictEqual(ctx.response.get('Foo-empty-String'), '')\n    assert.strictEqual(ctx.response.get('Foo-Number'), 0)\n    assert.ok(ctx.response.get('foo-NULL') === null)\n    assert.ok(typeof ctx.response.get('FOO-undefined') === 'undefined')\n  })\n\n  it('should return undefined for non-existent headers', () => {\n    const ctx = context()\n    assert.strictEqual(ctx.response.get('nonexistent'), undefined)\n    assert.strictEqual(ctx.response.get(''), undefined)\n  })\n})\n"
  },
  {
    "path": "__tests__/response/has.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/response/header.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('supertest')\nconst response = require('../../test-helpers/context').response\nconst Koa = require('../..')\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.deepStrictEqual(res.header, { 'x-foo': 'bar', 'x-number': 200 })\n  })\n\n  it('should use res.getHeaders() accessor when available', () => {\n    const res = response()\n    res.res._headers = null\n    res.res.getHeaders = () => ({ 'x-foo': 'baz' })\n    assert.deepStrictEqual(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 = Object.assign({}, ctx.response.header)\n    })\n\n    await request(app.callback())\n      .get('/')\n\n    assert.deepStrictEqual(header, { 'x-foo': '42' })\n  })\n\n  describe('when res._headers not present', () => {\n    it('should return empty object', () => {\n      const res = response()\n      res.res._headers = null\n      assert.deepStrictEqual(res.header, {})\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/response/headers.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response = require('../../test-helpers/context').response\n\ndescribe('res.header', () => {\n  it('should return the response header object', () => {\n    const res = response()\n    res.set('X-Foo', 'bar')\n    assert.deepStrictEqual(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      res.res._headers = null\n      assert.deepStrictEqual(res.headers, {})\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/response/inspect.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst response = require('../../test-helpers/context').response\nconst assert = require('node:assert/strict')\nconst util = require('util')\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      delete res.res\n      assert.strictEqual(res.inspect(), undefined)\n      assert.strictEqual(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.deepStrictEqual(res.inspect(), expected)\n    assert.deepStrictEqual(util.inspect(res), util.inspect(expected))\n  })\n})\n"
  },
  {
    "path": "__tests__/response/is.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst assert = require('node:assert/strict')\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": "__tests__/response/last-modified.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response = require('../../test-helpers/context').response\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": "__tests__/response/length.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst response = require('../../test-helpers/context').response\nconst assert = require('node:assert/strict')\nconst fs = require('fs')\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 = null\n        assert.strictEqual(res.length, undefined)\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  describe('and a .type is set to json', () => {\n    describe('and a .body is set to null', () => {\n      it('should return a number', () => {\n        const res = response()\n\n        res.type = 'json'\n        res.body = null\n        assert.strictEqual(res.length, 4)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/response/message.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response = require('../../test-helpers/context').response\n\ndescribe('res.message', () => {\n  it('should return the response status message', () => {\n    const res = response()\n    res.status = 200\n    assert.strictEqual(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.strictEqual(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.strictEqual(res.res.statusMessage, 'ok')\n    assert.strictEqual(res.inspect().message, 'ok')\n  })\n})\n"
  },
  {
    "path": "__tests__/response/redirect.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request = require('supertest')\nconst context = require('../../test-helpers/context')\nconst Koa = require('../..')\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.strictEqual(ctx.response.header.location, 'http://google.com/')\n    assert.strictEqual(ctx.status, 302)\n  })\n\n  it('should formatting url before redirect', () => {\n    const ctx = context()\n    ctx.redirect('http://google.com\\\\@apple.com')\n    assert.strictEqual(ctx.response.header.location, 'http://google.com/@apple.com')\n    assert.strictEqual(ctx.status, 302)\n  })\n\n  it('should formatting url before redirect', () => {\n    const ctx = context()\n    ctx.redirect('HTTP://google.com\\\\@apple.com')\n    assert.strictEqual(ctx.response.header.location, 'http://google.com/@apple.com')\n    assert.strictEqual(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('http://google.com/😓')\n    })\n\n    const res = await request(app.callback())\n      .get('/')\n\n    assert.strictEqual(res.status, 302)\n    assert.strictEqual(res.headers.location, 'http://google.com/%F0%9F%98%93')\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.strictEqual(ctx.response.header['content-type'], 'text/html; charset=utf-8')\n      assert.strictEqual(ctx.body, `Redirecting to ${url}.`)\n    })\n\n    it('should escape the url', () => {\n      const ctx = context()\n      let url = '<script>'\n      ctx.header.accept = 'text/html'\n      ctx.redirect(url)\n      url = escape(url)\n      assert.strictEqual(ctx.response.header['content-type'], 'text/html; charset=utf-8')\n      assert.strictEqual(ctx.body, `Redirecting to ${url}.`)\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.strictEqual(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.strictEqual(ctx.status, 301)\n      assert.strictEqual(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.strictEqual(ctx.status, 302)\n      assert.strictEqual(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'\n      ctx.header.accept = 'text/plain'\n      ctx.redirect('http://google.com')\n      assert.strictEqual(ctx.status, 302)\n      assert.strictEqual(ctx.body, `Redirecting to ${url}/.`)\n      assert.strictEqual(ctx.type, 'text/plain')\n    })\n  })\n})\n\nfunction escape (html) {\n  return String(html)\n    .replace(/&/g, '&amp;')\n    .replace(/\"/g, '&quot;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n}\n"
  },
  {
    "path": "__tests__/response/remove.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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": "__tests__/response/set.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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.get('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": "__tests__/response/socket.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response = require('../../test-helpers/context').response\nconst Stream = require('stream')\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": "__tests__/response/status.test.js",
    "content": "'use strict'\n\nconst { describe, it, beforeEach } = require('node:test')\nconst response = require('../../test-helpers/context').response\nconst request = require('supertest')\nconst statuses = require('statuses')\nconst assert = require('node:assert/strict')\nconst Koa = require('../..')\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(() => { statuses['700'] = 'custom status' })\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(!res.res.statusMessage)\n      })\n    })\n  })\n\n  describe('when a status string', () => {\n    it('should throw', () => {\n      assert.throws(() => { response().status = 'forbidden' }, /status code must be a number/)\n    })\n  })\n\n  function strip (status) {\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(ctx.response.header['content-type'] == null)\n        assert(ctx.response.header['content-length'] == null)\n        assert(ctx.response.header['transfer-encoding'] == null)\n      })\n\n      const res = await request(app.callback())\n        .get('/')\n        .expect(status)\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), false)\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-encoding'), false)\n      assert.strictEqual(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())\n        .get('/')\n        .expect(status)\n\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), false)\n      assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-encoding'), false)\n      assert.strictEqual(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": "__tests__/response/type.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst assert = require('node:assert/strict')\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(!ctx.type)\n      assert(!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(!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  describe('when setting to +json content type', () => {\n    it('should set the content type to json', () => {\n      const ctx = context()\n      ctx.type = 'application/vnd.myapi.v1+json'\n      assert.strictEqual(ctx.type, 'application/vnd.myapi.v1+json')\n    })\n  })\n})\n"
  },
  {
    "path": "__tests__/response/vary.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context = require('../../test-helpers/context')\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      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": "__tests__/response/writable.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Koa = require('../../')\nconst net = require('net')\nconst { once } = require('events')\n\ndescribe('res.writable', () => {\n  describe('when continuous requests in one persistent connection', () => {\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      const port = server.address().port\n      const buf = Buffer.from('GET / HTTP/1.1\\r\\nHost: localhost:' + port + '\\r\\nConnection: keep-alive\\r\\n\\r\\n')\n\n      const client = net.connect(port)\n      const datas = []\n\n      client.on('data', data => datas.push(data))\n\n      await once(client, 'connect')\n      client.write(buf)\n      client.write(buf)\n      client.end()\n\n      await once(client, 'end')\n      client.destroy()\n\n      const responses = Buffer.concat(datas).toString()\n      assert.strictEqual(/request 1, writable: true/.test(responses), true)\n      assert.strictEqual(/request 2, writable: true/.test(responses), true)\n      assert.strictEqual(count, 2) // Add assertion for request count\n\n      server.close()\n      await once(server, 'close')\n    })\n  })\n\n  describe('when socket closed before response sent', () => {\n    it('should not be writable', async () => {\n      const app = new Koa()\n      let ctx\n      let assertionRan = false\n      app.on('error', () => {})\n\n      app.use(c => {\n        ctx = c\n        assertionRan = true\n      })\n\n      const server = app.listen()\n      const port = server.address().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      await once(client, 'connect')\n      client.write(buf)\n\n      await new Promise(resolve => setTimeout(resolve, 5))\n      client.destroy()\n\n      server.close()\n      await once(server, 'close')\n\n      assert(ctx)\n      assert(assertionRan)\n      assert(!ctx.writable)\n    })\n  })\n\n  describe('when response finished', () => {\n    it('should not be writable', async () => {\n      const app = new Koa()\n      let assertionRan = false\n\n      app.use(ctx => {\n        ctx.res.end()\n        assert(!ctx.writable)\n        assertionRan = true\n      })\n\n      const server = app.listen()\n      const port = server.address().port\n      const buf = Buffer.from('GET / HTTP/1.1\\r\\nHost: localhost:' + port + '\\r\\nConnection: keep-alive\\r\\n\\r\\n')\n\n      const client = net.connect(port)\n      await once(client, 'connect')\n      client.write(buf)\n\n      await new Promise(resolve => setTimeout(resolve, 5))\n\n      client.destroy()\n\n      server.close()\n      await once(server, 'close')\n\n      assert(assertionRan)\n    })\n  })\n})\n"
  },
  {
    "path": "docs/api/context.md",
    "content": "# Context\n\n  A Koa Context encapsulates Node's `request` and `response` objects\n  into a single object which provides many helpful methods for writing\n  web applications and APIs.\n  These operations are used so frequently in HTTP server development\n  that they are added at this level instead of a higher level framework,\n  which would force middleware to re-implement this common functionality.\n\n  A `Context` is created _per_ request, and is referenced in middleware\n  as the receiver, or the `ctx` identifier, as shown in the following\n  snippet:\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\n  Many of the context's accessors and methods simply delegate to their `ctx.request` or `ctx.response`\n  equivalents for convenience, and are otherwise identical. For example `ctx.type` and `ctx.length`\n  delegate 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\n  Node's `request` object.\n\n### ctx.res\n\n  Node's `response` object.\n\n  Bypassing 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\n  A Koa `Request` object.\n\n### ctx.response\n\n  A Koa `Response` object.\n\n### ctx.state\n\n  The 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\n  Application instance reference.\n\n### ctx.app.emit\n\n  Koa 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\n  Get 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\n  Set 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* `partitioned`: a boolean indicating whether to partition the cookie in Chrome for the [CHIPS Update](https://developers.google.com/privacy-sandbox/3pcd/chips) (`false` by default). If this is true, Cookies from embedded sites will be partitioned and only readable from the same top level site from which it was created.\n* `priority`: a string indicating the cookie priority. This can be set to `'low'`, `'medium'`, or `'high'`.\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\n  Helper method to throw an error with a `.status` property\n  defaulting to `500` that will allow Koa to respond appropriately.\n  The following combinations are allowed:\n\n```js\nctx.throw(400);\nctx.throw(400, 'name required');\nctx.throw(400, 'name required', { user: user });\n```\n\n  For 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\n  Note that these are user-level errors and are flagged with\n  `err.expose` meaning the messages are appropriate for\n  client responses, which is typically not the case for\n  error messages since you do not want to leak failure\n  details.\n\n  You 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\n  Helper method to throw an error similar to `.throw()`\n  when `!value`. Similar to Node's [assert()](http://nodejs.org/api/assert.html)\n  method.\n\n```js\nctx.assert(ctx.state.user, 401, 'User not found. Please login!');\n```\n\nKoa uses [http-assert](https://github.com/jshttp/http-assert) for assertions.\n\n### ctx.respond\n\n  To 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\n  Note 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\n  The 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\n  The following accessors and alias [Response](response.md) equivalents:\n\n  - `ctx.body`\n  - `ctx.body=`\n  - `ctx.has()`\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.vary()`\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  - `ctx.writable`\n"
  },
  {
    "path": "docs/api/index.md",
    "content": "# Installation\n\n  Koa requires __node v18.0.0__ or higher for ES2015 and async function support.\n\n  You can quickly install a supported version of Node with your favorite version manager:\n\n```bash\n$ nvm install 22\n$ npm i koa\n$ node my-koa-app.js\n```\n\n# Application\n\n  A Koa application is an object containing an array of middleware functions\n  which are composed and executed in a stack-like manner upon request. Koa is similar to many\n  other middleware systems that you may have encountered such as Ruby's Rack, Connect, and so on -\n  however a key design decision was made to provide high level \"sugar\" at the otherwise low-level\n  middleware layer. This improves interoperability, robustness, and makes writing middleware much\n  more enjoyable.\n\n  This includes methods for common tasks like content-negotiation, cache freshness, proxy support, and redirection\n  among others. Despite supplying a reasonably large number of helpful methods Koa maintains a small footprint, as\n  no middleware are bundled.\n\n  The obligatory hello world application:\n\n<!-- runkit:endpoint -->\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\n  Koa middleware cascade in a more traditional way as you may be used to with similar tools -\n  this was previously difficult to make user friendly with Node's use of callbacks.\n  However with async functions we can achieve \"true\" middleware. Contrasting Connect's implementation which\n  simply passes control through series of functions until one returns, Koa invoke \"downstream\", then\n  control flows back \"upstream\".\n\n  The following example responds with \"Hello World\", however first the request flows through\n  the `x-response-time` and `logging` middleware to mark when the request started, then yields control through the response middleware. When a middleware invokes `next()`\n  the function suspends and passes control to the next middleware defined. After there are no more\n  middleware to execute downstream, the stack will unwind and each middleware is resumed to perform\n  its upstream behaviour.\n\n<!-- runkit:endpoint -->\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\n  Application settings are properties on the `app` instance, currently\n  the 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  - `app.asyncLocalStorage<Boolean|asyncLocalStorage>` - pass `true` or an instance of `AsyncLocalStorage` to enable async local storage. See below for details.\n\n  You can pass the settings to the constructor:\n  ```js\n  const Koa = require('koa');\n  const app = new Koa({ proxy: true });\n  ```\n  or dynamically:\n  ```js\n  const Koa = require('koa');\n  const app = new Koa();\n  app.proxy = true;\n  ```\n\n## app.listen(...)\n\n  A Koa application is not a 1-to-1 representation of an HTTP server.\n  One or more Koa applications may be mounted together to form larger\n  applications with a single HTTP server.\n\n  Create 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\n  The `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\n  This means you can spin up the same application as both HTTP and HTTPS\n  or 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\n  Return a callback function suitable for the `http.createServer()`\n  method to handle a request.\n  You may also use this callback function to mount your Koa app in a\n  Connect/Express app.\n\n## app.use(function)\n\n  Add the given middleware function to this application.\n  `app.use()` returns `this`, so is chainable.\n```js\napp.use(someMiddleware)\napp.use(someOtherMiddleware)\napp.listen(3000)\n```\n  Is the same as\n```js\napp.use(someMiddleware)\n  .use(someOtherMiddleware)\n  .listen(3000)\n```\n\n  See [Middleware](https://github.com/koajs/koa/wiki#middleware) for\n  more information.\n\n## app.keys=\n\n  Set signed cookie keys.\n\n  These are passed to [KeyGrip](https://github.com/crypto-utils/keygrip),\n  however you may also pass your own `KeyGrip` instance. For\n  example the following are acceptable:\n\n```js\napp.keys = ['OEK5zjaAMPc3L6iK7PyUjCOziUH3rsrMKB9u8H07La1SkfwtuBoDnHaaPCkG5Brg', 'MNKeIebviQnCPo38ufHcSfw3FFv8EtnAe1xE02xkN1wkCV1B2z126U44yk2BQVK7'];\napp.keys = new KeyGrip(['OEK5zjaAMPc3L6iK7PyUjCOziUH3rsrMKB9u8H07La1SkfwtuBoDnHaaPCkG5Brg', 'MNKeIebviQnCPo38ufHcSfw3FFv8EtnAe1xE02xkN1wkCV1B2z126U44yk2BQVK7'], 'sha256');\n```\n\n  For security reasons, please ensure that the key is long enough and random.\n\n  These keys may be rotated and are used when signing cookies\n  with 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.\n  You may add additional properties to `ctx` by editing `app.context`.\n  This is useful for adding properties or methods to `ctx` to be used across your entire app,\n  which may be more performant (no middleware) and/or easier (fewer `require()`s)\n  at the expense of relying more on `ctx`, which could be considered an anti-pattern.\n\n  For 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## app.currentContext\n\n> New in Koa v3.\n\nIf `asyncLocalStorage` is enabled, this will return the current context for the request. For example:\n\n```js\nconst app = new Koa({ asyncLocalStorage: true })\n\napp.use(async (ctx, next) => {\n  callSomeFunction()\n})\n\nfunction callSomeFunction () {\n  app.currentContext = {} /* ctx of the middleware above */\n}\n```\n\nStarting in v3.1.0, you can also pass your own AsyncLocalStorage instance:\n\n```js\nconst asyncLocalStorage = new AsyncLocalStorage()\nconst app = new Koa({ asyncLocalStorage })\n\napp.use(async (ctx, next) => {\n  callSomeFunction()\n})\n\nfunction callSomeFunction () {\n  const ctx = asyncLocalStorage.getStore()\n}\n```\n\nUse this to pass key request details like request ID or the user to your internal services.\n\n## Error Handling\n\n  By default outputs all errors to stderr unless `app.silent` is `true`.\n  The default error handler also won't output errors when `err.status` is `404` or `err.expose` is `true`.\n  To 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\n  If 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\n  When 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\n  appropriately with a 500 \"Internal Server Error\". In either case\n  an app-level \"error\" is emitted for logging purposes.\n"
  },
  {
    "path": "docs/api/request.md",
    "content": "# Request\n\n  A Koa `Request` object is an abstraction on top of Node's vanilla request object,\n  providing additional functionality that is useful for every day HTTP server\n  development.\n\n## API\n\n### request.header\n\n Request header object.  This is the same as the [`headers`](https://nodejs.org/api/http.html#http_message_headers) field\n on Node's [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage).\n\n### request.header=\n\n Set request header object.\n\n### request.headers\n\n Request header object. Alias as `request.header`.\n\n### request.headers=\n\n  Set request header object. Alias as `request.header=`.\n\n### request.method\n\n  Request method.\n\n### request.method=\n\n  Set request method, useful for implementing middleware\n  such as `methodOverride()`.\n\n### request.length\n\n  Return request Content-Length as a number when present, or `undefined`.\n\n### request.url\n\n  Get request URL.\n\n### request.url=\n\n  Set request URL, useful for url rewrites.\n\n### request.originalUrl\n\n  Get request original URL.\n\n### request.origin\n\n  Get origin of URL, include `protocol` and `host`.\n\n```js\nctx.request.origin\n// => http://example.com\n```\n\n### request.href\n\n  Get 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\n  Get request pathname.\n\n### request.path=\n\n  Set request pathname and retain query-string when present.\n\n### request.querystring\n\n  Get raw query string void of `?`.\n\n### request.querystring=\n\n  Set raw query string.\n\n### request.search\n\n  Get raw query string with the `?`.\n\n### request.search=\n\n  Set raw query string.\n\n### request.host\n\n  Get host (hostname:port) when present. Supports `X-Forwarded-Host`\n  when `app.proxy` is __true__, otherwise `Host` is used.\n\n### request.hostname\n\n  Get hostname when present. Supports `X-Forwarded-Host`\n  when `app.proxy` is __true__, otherwise `Host` is used.\n\n  If 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\n  Get WHATWG parsed URL object.\n\n### request.type\n\n  Get 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\n  Get request charset when present, or `undefined`:\n\n```js\nctx.request.charset;\n// => \"utf-8\"\n```\n\n### request.query\n\n  Get parsed query-string, returning an empty object when no\n  query-string is present. Note that this getter does _not_\n  support nested parsing.\n\n  For example \"color=blue&size=small\":\n\n```js\n{\n  color: 'blue',\n  size: 'small'\n}\n```\n\n### request.query=\n\n  Set query-string to the given object. Note that this\n  setter does _not_ support nested objects.\n\n```js\nctx.query = { next: '/login' };\n```\n\n### request.fresh\n\n  Check if a request cache is \"fresh\", aka the contents have not changed. This\n  method 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\n  Inverse of `request.fresh`.\n\n### request.protocol\n\n  Return request protocol, \"https\" or \"http\". Supports `X-Forwarded-Proto`\n  when `app.proxy` is __true__.\n\n### request.secure\n\n  Shorthand for `ctx.protocol == \"https\"` to check if a request was\n  issued via TLS.\n\n### request.ip\n\n  Request remote address. Supports `X-Forwarded-For` when `app.proxy`\n  is __true__.\n\n### request.ips\n\n  When `X-Forwarded-For` is present and `app.proxy` is enabled an array\n  of these ips is returned, ordered from upstream -> downstream. When\n  disabled an empty array is returned.\n\n  For example if the value were \"client, proxy1, proxy2\",\n  you would receive the array `[\"client\", \"proxy1\", \"proxy2\"]`.\n\n  Most of the reverse proxy(nginx) set x-forwarded-for via\n  `proxy_add_x_forwarded_for`, which poses a certain security risk.\n  A malicious attacker can forge a client's ip address by forging\n  a `X-Forwarded-For`request header. The request sent by the client\n  has an `X-Forwarded-For` request header for 'forged'. After being\n  forwarded by the reverse proxy, `request.ips` will be\n  ['forged', 'client', 'proxy1', 'proxy2'].\n\n  Koa offers two options to avoid being bypassed.\n\n  If you can control the reverse proxy, you can avoid bypassing\n  by adjusting the configuration, or use the `app.proxyIpHeader`\n  provided by koa to avoid reading `x-forwarded-for` to get ips.\n\n  ```js\n  const app = new Koa({\n    proxy: true,\n    proxyIpHeader: 'X-Real-IP',\n  });\n  ```\n\n  If you know exactly how many reverse proxies are in front of\n  the server, you can avoid reading the user's forged request\n  header by configuring `app.maxIpsCount`:\n\n  ```js\n  const 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\n  Return subdomains as an array.\n\n  Subdomains are the dot-separated parts of the host before the main domain of\n  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, `ctx.subdomains` is `[\"ferrets\", \"tobi\"]`.\n  If `app.subdomainOffset` is 3, `ctx.subdomains` is `[\"tobi\"]`.\n\n### request.is(types...)\n\n  Check if the incoming request contains the \"Content-Type\"\n  header field, and it contains any of the give mime `type`s.\n  If there is no request body, `null` is returned.\n  If there is no content type, or the match fails `false` is returned.\n  Otherwise, 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\n  For example if you want to ensure that\n  only 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\n  Koa'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\n  Check 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\n  such as \"application/json\", the extension name\n  such 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\n  You may call `ctx.accepts()` as many times as you like,\n  or use a switch:\n\n```js\nswitch (ctx.accepts('json', 'html', 'text')) {\n  case 'json': break;\n  case 'html': break;\n  case 'text': break;\n  default: ctx.throw(406, 'json, html, or text only');\n}\n```\n\n### request.acceptsEncodings(encodings)\n\n  Check 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\n  When no arguments are given all accepted encodings\n  are returned as an array:\n\n```js\n// Accept-Encoding: gzip, deflate\nctx.acceptsEncodings();\n// => [\"gzip\", \"deflate\", \"identity\"]\n```\n\n  Note 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\n  Check if `charsets` are acceptable, returning\n  the 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\n  When no arguments are given all accepted charsets\n  are 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\n  Check if `langs` are acceptable, returning\n  the 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\n  When no arguments are given all accepted languages\n  are 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\n  Check if the request is idempotent (e.g. a `GET`, `HEAD`, etc. request).\n\n### request.socket\n\n  Return the request socket.\n\n### request.get(field)\n\n  Return request header with case-insensitive `field`.\n"
  },
  {
    "path": "docs/api/response.md",
    "content": "# Response\n\n  A Koa `Response` object is an abstraction on top of Node's vanilla response object,\n  providing additional functionality that is useful for every day HTTP server\n  development.\n\n## API\n\n### response.header\n\n  Response header object.\n\n### response.headers\n\n  Response header object. Alias as `response.header`.\n\n\n### response.socket\n\n  Response socket. Points to net.Socket instance as `request.socket`.\n\n### response.status\n\n  Get response status. By default, `response.status` is set to `404` unlike Node's `res.statusCode` which defaults to `200`.\n\n### response.status=\n\n  Set 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\n  Since `response.status` default is set to `404`, to send a response\n  without 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\n  Get response status message. By default, `response.message` is\n  associated with `response.status`.\n\n### response.message=\n\n  Set response status message to the given value.\n\n### response.length=\n\n  Set response Content-Length to the given value.\n\n### response.length\n\n  Return response Content-Length as a number when present, or deduce\n  from `ctx.body` when possible, or `undefined`.\n\n### response.body\n\n  Get response body.\n\n### response.body=\n\n  Set 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\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\n  The Content-Type is defaulted to text/html or text/plain, both with\n  a default charset of utf-8. The Content-Length field is also set.\n\n#### Buffer\n\n  The Content-Type is defaulted to application/octet-stream, and Content-Length\n  is also set.\n\n#### Stream\n\n  The Content-Type is defaulted to application/octet-stream.\n\n  Whenever a stream is set as the response body, `.onerror` is automatically added as a listener to the `error` event to catch any errors.\n  In addition, whenever the request is closed (even prematurely), the stream is destroyed.\n  If you do not want these two features, do not set the stream as the body directly.\n  For 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\n  See: https://github.com/koajs/koa/pull/612 for more information.\n\n  Here'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\n  The Content-Type is defaulted to application/json. This includes plain objects `{ foo: 'bar' }` and arrays `['foo', 'bar']`.\n\n### response.get(field)\n\n  Get 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\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```js\nconst rateLimited = ctx.response.has('X-RateLimit-Limit');\n```\n\n### response.set(field, value)\n\n  Set response header `field` to `value`:\n\n```js\nctx.set('Cache-Control', 'no-cache');\n```\n\n### response.append(field, value)\n  Append additional header `field` with value `val`.\n\n```js\nctx.append('Link', '<http://127.0.0.1/>');\n```\n\n### response.set(fields)\n\n  Set 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\n  Remove header `field`.\n\n### response.type\n\n  Get 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\n  Set 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\n  Note: when appropriate a `charset` is selected for you, for\n  example `response.type = 'html'` will default to \"utf-8\". If you need to overwrite `charset`,\n  use `ctx.set('Content-Type', 'text/html')` to set response header field to value directly.\n\n### response.is(types...)\n\n  Very similar to `ctx.request.is()`.\n  Check whether the response type is one of the supplied types.\n  This is particularly useful for creating middleware that\n  manipulate responses.\n\n  For example, this is a middleware that minifies\n  all 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)\n\n  Perform a [302] redirect to `url`.\n\n```js\nctx.redirect('/login');\nctx.redirect('http://google.com');\n```\n\n  To alter the default status of `302`, simply assign the status\n  before 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.back(url)\n\n  Similar to `.redirect(url)`, but first checks the `referer` header to redirect.\n  This is new in v3 as `.redirect(url, alt)` removes the special case `url = 'back'` option.\n\n### response.attachment([filename], [options])\n\n  Set `Content-Disposition` to \"attachment\" to signal the client\n  to prompt for download. Optionally specify the `filename` of the\n  download and some [options](https://github.com/jshttp/content-disposition#options).\n\n### response.headerSent\n\n  Check if a response header has already been sent. Useful for seeing\n  if the client may be notified on error.\n\n### response.lastModified\n\n  Return the `Last-Modified` header as a `Date`, if it exists.\n\n### response.lastModified=\n\n  Set the `Last-Modified` header as an appropriate UTC string.\n  You can either set it as a `Date` or date string.\n\n```js\nctx.response.lastModified = new Date();\n```\n\n### response.etag=\n\n  Set the ETag of a response including the wrapped `\"`s.\n  Note 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\n  Vary on `field`.\n\n### response.flushHeaders()\n\n  Flush any set headers to the client. This is a proxy for [`res.flushHeaders()`](https://nodejs.org/api/http.html#requestflushheaders)\n\n### response.writable\n\n  A boolean that indicates whether the response is still writable. \n"
  },
  {
    "path": "docs/error-handling.md",
    "content": "# Error Handling\n\n## Try-Catch\n\n  Using async functions means that you can try-catch `next`.\n  This example adds a `.status` to all errors:\n\n  ```js\n  app.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\n  The default error handler is essentially a `try-catch` at\n  the very beginning of the middleware chain. To use a\n  different error handler, simply put another `try-catch` at\n  the beginning of the middleware chain, and handle the error\n  there. However, the default error handler is good enough for\n  most use cases. It will use a status code of `err.status`,\n  or by default 500. If `err.expose` is true, then `err.message`\n  will be the reply. Otherwise, a message generated from the\n  error code will be used (e.g. for the code 500 the message\n  \"Internal Server Error\" will be used). All headers will be\n  cleared from the request, but any headers in `err.headers`\n  will then be set. You can use a `try-catch`, as specified\n  above, to add a header to this list.\n\n  Here 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\n  Error event listeners can be specified with `app.on('error')`.\n  If no error listener is specified, a default error listener\n  is used. Error listeners receive all errors that make their\n  way back through the middleware chain, if an error is caught\n  and not thrown again, it will not be passed to the error\n  listener. 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`\n  is 404.\n"
  },
  {
    "path": "docs/faq.md",
    "content": "# Frequently Asked Questions\n\n## Does Koa replace Express?\n\n  It's more like Connect, but a lot of the Express goodies\n  were moved to the middleware level in Koa to help form\n  a stronger foundation. This makes middleware more enjoyable\n  and less error-prone to write, for the entire stack, not\n  just the end application code.\n\n  Typically many middleware would\n  re-implement similar features, or even worse incorrectly implement them,\n  when features like signed cookie secrets among others are typically application-specific,\n  not middleware specific.\n\n## Does Koa replace Connect?\n\n  No, just a different take on similar functionality\n  now that async functions allow us to write code with fewer\n  callbacks. Connect is equally capable, and some may still prefer it,\n  it's up to what you prefer.\n\n## Does Koa include routing?\n\n  No - out of the box Koa has no form of routing, however\n  many routing middleware are available: https://github.com/koajs/koa/wiki\n\n## Why isn't Koa just Express 4.0?\n\n  Koa is a pretty large departure from what people know about Express,\n  the design is fundamentally much different, so the migration from\n  Express 3.0 to this Express 4.0 would effectively mean rewriting\n  the entire application, so we thought it would be more appropriate\n  to create a new library.\n\n## What custom properties do the Koa objects have?\n\n  Koa uses its own custom objects: `ctx`, `ctx.request`, and `ctx.response`.\n  These objects abstract Node's `req` and `res` objects with convenience methods and getters/setters.\n  Generally, 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": "docs/guide.md",
    "content": "\n# Guide\n\n  This 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  - [HTTP2](#http2)\n  - [Server-Sent Events](#server-sent-events)\n\n## Writing Middleware\n\n  Koa middleware are simple functions with the signature `(ctx, next)` (i.e. a `MiddlewareFunction`). When\n  the middleware is run, it must manually invoke `next()` to run the \"downstream\" middleware.\n\n  For 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\n  If you're a front-end developer you can think any code before `next();` as the \"capture\" phase,\n  while any code after is the \"bubble\" phase. This crude gif illustrates how async function allow us\n  to properly utilize stack flow to implement request and response flows:\n\n![Koa middleware](/docs/middleware.gif)\n\n   1. Create a date to track response time\n   2. Await control to the next middleware\n   3. Create another date to track duration\n   4. Await control to the next middleware\n   5. Set the response body to \"Hello World\"\n   6. Calculate duration time\n   7. Output log line\n   8. Calculate response time\n   9. Set `X-Response-Time` header field\n   10. Hand off to Koa to handle the response\n\n Next we'll look at the best practices for creating Koa middleware.\n\n## Middleware Best Practices\n\n  This section covers middleware authoring best practices, such as middleware\n  accepting options, named middleware for debugging, among others.\n\n### Middleware options\n\n  When creating a public middleware, it's useful to conform to the convention of\n  wrapping the middleware in a function that accepts options, allowing users to\n  extend functionality. Even if your middleware accepts _no_ options, this is still\n  a good idea to keep things uniform.\n\n  Here our contrived `logger` middleware accepts a `format` string for customization,\n  and 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\n      .replace(':method', ctx.method)\n      .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\n  Naming 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```\n\n### Combining multiple middleware with koa-compose\n\n  Sometimes 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\n  Middleware that decide to respond to a request and wish to bypass downstream middleware may\n  simply omit `next()`. Typically this will be in routing middleware, but this can be performed by\n  any. For example the following will respond with \"two\", however all three are executed, giving the\n  downstream \"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\n  The following configuration omits `next()` in the second middleware, and will still respond\n  with \"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\n  When the furthest downstream middleware executes `next();`, it's really yielding to a noop\n  function, allowing the middleware to compose correctly anywhere in the stack.\n\n## Async operations\n\n  Async function and promise forms Koa's foundation, allowing\n  you to write non-blocking sequential code. For example this middleware reads the filenames from `./docs`,\n  and then reads the contents of each markdown file in parallel before assigning the body to the joint result.\n\n\n```js\nconst fs = require('fs').promises;\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\n  Koa, along with many of the libraries it's built, can be debugged using Node.js's built-in util.debuglog, which provides simple conditional logging via the __NODE_DEBUG__ environment variable.\n\n  For example\n  to see all Koa-specific debugging information just pass `NODE_DEBUG=koa*` and upon boot you'll see the list of middleware used, among other things.\n\n```\n$ NODE_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\n  Since JavaScript does not allow defining function names at\n  runtime, you can also set a middleware's name as `._name`.\n  This is useful when you don't have control of a middleware's name.\n  For example:\n\n```js\nconst path = require('node:path');\nconst serve = require('koa-static');\n\nconst publicFiles = serve(path.join(__dirname, 'public'));\npublicFiles._name = 'static /public';\n\napp.use(publicFiles);\n```\n\n  Now, instead of just seeing \"serve\" when debugging, you will see:\n\n```\n  koa:application use static /public +0ms\n```\n\nThis lets you use Koa’s internal debug logs without any third-party dependencies by relying on node:util's built-in debuglog.\n\n## HTTP2\n\nExample of setting up an HTTP2 server with Koa using the HTTP compatibility layer:\n\n```js\nimport Koa from 'koa'\nimport http2 from 'node:http2'\nimport fs from 'node:fs'\n\nconst app = new Koa();\n\nconst onRequestHandler = app.callback();\nconst serverOptions = {\n  key: fs.readFileSync('key.pem'),\n  cert: fs.readFileSync('cert.pem')\n}\n\nconst server = http2.createSecureServer(serverOptions, onRequestHandler);\nserver.listen(3000);\n```\n\n## Server-Sent Events\n\nAn example of using server-sent events with Koa:\n\n```js\nimport { PassThrough } from 'node:stream'\nimport OpenAI from 'openai'\nimport Koa from 'koa'\n\nconst openai = new OpenAI({\n  apiKey: process.env.OPENAI_API_KEY,\n})\n\nconst app = new Koa()\n\napp.use(async (ctx, next) => { // TODO: use your favorite routing library\n  const { message } = await ctx.request.json() // this example uses koa-body-parsers\n\n  const stream = await openai.chat.completions.create({\n    model: 'gpt-4o-mini',\n    messages: [{ role: 'user', content: message }],\n    stream: true,\n  })\n\n  ctx.response.type = 'text/event-stream'\n  const body = ctx.body = new PassThrough()\n\n  // streaming needs to be handled in a separate event loop\n  ;(async () => {\n    for await (const chunk of stream) {\n      const content = chunk.choices[0]?.delta?.content\n      if (!content) continue\n\n      body.write(`data: ${JSON.stringify({\n        delta: {\n          content: chunk.choices[0].delta.content || ''\n        }\n      })}\\n\\n`)\n    }\n    body.end()\n  })().catch((err) => app.emit('error', err))\n})\n\napp.listen(3000)\n```\n"
  },
  {
    "path": "docs/koa-vs-express.md",
    "content": "# Koa vs Express\n\n  Philosophically, Koa aims to \"fix and replace Node\", whereas Express \"augments Node\".\n  Koa uses promises and async functions to rid apps of callback hell and simplify error handling.\n  It exposes its own `ctx.request` and `ctx.response` objects instead of Node's `req` and `res` objects.\n\n  Express, on the other hand, augments Node's `req` and `res` objects with additional properties and methods\n  and includes many other \"framework\" features, such as routing and templating, which Koa does not.\n\n  Thus, 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\n> NOTE: this doesn't mean Koa is incapable of supporting these features, these features are simply supported by other modules (middleware, plugins, etc.) versus the framework itself.\n\n  Thus, 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.\n  If you want to get rid of callbacks, use Koa.\n\n  As 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\n  It's more like Connect, but a lot of the Express goodies\n  were moved to the middleware level in Koa to help form\n  a stronger foundation. This makes middleware more enjoyable\n  and less error-prone to write, for the entire stack, not\n  just the end application code.\n\n  Typically many middleware would\n  re-implement similar features, or even worse incorrectly implement them,\n  when features like signed cookie secrets among others are typically application-specific,\n  not middleware-specific.\n\n## Does Koa replace Connect?\n\n  No, just a different take on similar functionality\n  now that generators allow us to write code with fewer\n  callbacks. Connect is equally capable, and some may still prefer it,\n  it's up to what you prefer.\n\n## Why isn't Koa just Express 4.0?\n\n  Koa is a pretty large departure from what people know about Express,\n  the design is fundamentally much different, so the migration from\n  Express 3.0 to this Express 4.0 would effectively mean rewriting\n  the entire application, so we thought it would be more appropriate\n  to create a new library.\n\n## How is Koa different from Connect/Express?\n\n### Promises-based control flow\n\n  No callback hell.\n\n  Better error handling through try/catch.\n\n  No need for domains.\n\n### Koa is barebones\n\n  Unlike both Connect and Express, Koa does not include any middleware.\n\n  Unlike Express, routing is not provided.\n\n  Unlike Express, many convenience utilities are not provided. For example, sending files.\n\n  Koa is more modular.\n\n### Koa relies less on middleware\n\n  For example, instead of a \"body parsing\" middleware, you would instead use a body parsing function.\n\n### Koa abstracts Node's request/response\n\n  Less hackery.\n\n  Better user experience.\n\n  Proper stream handling.\n\n### Koa routing (third party libraries support)\n\n  Since Express comes with its own routing, but Koa does not have\n  any built-in routing, there are third party libraries available such as\n  koa-router and koa-route.\n  Similarly, just like we have helmet for security in Express, for Koa\n  we have koa-helmet available and the list goes on for Koa available third\n  party libraries.\n"
  },
  {
    "path": "docs/migration-v1-to-v2.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(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## 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\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": "docs/migration-v2-to-v3.md",
    "content": "# Migrating from Koa v2.x to v3.x\n\n## Breaking Changes\n\n### Node.js Version Requirement\n\nKoa v3 requires **Node.js v18.0.0** or higher.\n\n### Removal of v1.x Middleware Support\n\nAs announced in Koa v2.x, support for the old middleware signature (generator functions) has been removed in v3.\n\nIf you were still using generator middleware with `koa-convert`, you'll need to update all middleware to use async functions or functions that return promises.\n\n```js\n// Old way (using koa-convert with generator middleware)\nconst convert = require('koa-convert');\n\napp.use(convert(function* (next) {\n  const data = yield fetchData();\n  yield next;\n  this.body = data;\n}));\n\n// New way (using async/await)\napp.use(async (ctx, next) => {\n  const data = await fetchData();\n  await next();\n  ctx.body = data;\n});\n```\n\n### HTTP Errors Update\n\nKoa v3 updates `http-errors` to v2.0.0, which changes the signature of `ctx.throw()`:\n\n```js\n// Old format in Koa v2.x\nctx.throw(status, message, properties)\n\n// New format in Koa v3.x\nctx.throw(status, error, properties)\n```\n\nSee the [http-errors documentation](https://www.npmjs.com/package/http-errors) for more details.\n\n### Redirect Changes\n\nThe `res.redirect('back')` method has been removed. Instead, use the new `ctx.back()` method:\n\n```js\n// Old way in Koa v2.x\nctx.response.redirect('back')\n\n// New way in Koa v3.x\nctx.back()\n```\n\n### QueryString Replacement\n\nNode's querystring module has been replaced with `URLSearchParams`. This should be mostly transparent to users, but might cause subtle differences in query string parsing.\n\n```js\n// Old way (Koa v2.x using querystring)\nconst qs = require('querystring');\napp.use(async (ctx, next) => {\n  const query = qs.parse(ctx.querystring);\n  // query['user[]'] = ['john', 'jane']\n  // query['items[0]'] = 'book'\n  await next();\n});\n\n// New way (Koa v3.x using URLSearchParams)\napp.use(async (ctx, next) => {\n  const query = new URLSearchParams(ctx.querystring);\n  // query.getAll('user') => ['john', 'jane']\n  // query.get('items') => 'book'\n  await next();\n});\n```\n\n## New Features\n\n### AsyncLocalStorage Support\n\nKoa v3 adds support for [AsyncLocalStorage](https://nodejs.org/api/async_context.html#class-asynclocalstorage), which allows you to access the current context from anywhere in your application:\n\n```js\n// Enable AsyncLocalStorage\nconst app = new Koa({ asyncLocalStorage: true })\n\napp.use(async (ctx, next) => {\n  callSomeFunction()\n  await next()\n})\n\nfunction callSomeFunction() {\n  // Access the current context\n  const ctx = app.currentContext\n  // Do something with ctx\n}\n```\n\nYou can also pass your own AsyncLocalStorage instance:\n\n```js\nconst { AsyncLocalStorage } = require('async_hooks')\nconst asyncLocalStorage = new AsyncLocalStorage()\nconst app = new Koa({ asyncLocalStorage })\n\napp.use(async (ctx, next) => {\n  callSomeFunction()\n  await next()\n})\n\nfunction callSomeFunction() {\n  // Access the current context\n  const ctx = asyncLocalStorage.getStore()\n  // Do something with ctx\n}\n```\n\n### Web WHATWG Support\n\nKoa v3 adds support for [Web WHATWG standards](https://spec.whatwg.org/), including:\n\n- Support for `Blob` objects as response bodies\n- Support for `ReadableStream` objects as response bodies\n- Support for `Response` objects as response bodies\n\n```js\napp.use(async ctx => {\n  // Using a Blob\n  ctx.body = new Blob(['Hello World'], { type: 'text/plain' })\n  \n  // Using a ReadableStream\n  ctx.body = new ReadableStream({\n    start(controller) {\n      controller.enqueue('Hello World')\n      controller.close()\n    }\n  })\n  \n  // Using a Response object\n  ctx.body = new Response('Hello World', { \n    headers: { 'Content-Type': 'text/plain' } \n  })\n})\n```\n\n## Upgrading Guide\n\n1. Update your Node.js version to v18.0.0 or higher\n2. Update all generator middleware to use async functions:\n\n   ```js\n   // Old way (generator middleware)\n   app.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   // New way (async middleware)\n   app.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   ```\n3. Update any calls to `ctx.throw()` to use the new signature:\n\n   ```js\n   // Old way (Koa v2.x)\n   ctx.throw(404, 'User not found', { user: 'john' });\n\n   // New way (Koa v3.x)\n   const error = new Error('User not found');\n   ctx.throw(404, error, { user: 'john' });\n\n   // You can also throw HTTP errors directly\n   const createError = require('http-errors');\n   ctx.throw(createError(404, 'User not found', { user: 'john' }));\n   ```\n4. Replace `ctx.response.redirect('back')` with `ctx.back()`\n5. Test your application thoroughly to ensure compatibility\n"
  },
  {
    "path": "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 afterward.\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": "lib/application.js",
    "content": "'use strict'\n\n/**\n * Module dependencies.\n */\nconst util = require('node:util')\nconst debug = util.debuglog('koa:application')\nconst Emitter = require('node:events')\nconst Stream = require('node:stream')\nconst http = require('node:http')\nconst { AsyncLocalStorage } = require('node:async_hooks')\n\nconst onFinished = require('on-finished')\nconst compose = require('koa-compose')\nconst statuses = require('statuses')\nconst { HttpError } = require('http-errors')\n\nconst request = require('./request')\nconst response = require('./response')\nconst context = require('./context')\nconst isStream = require('./is-stream.js')\nconst only = require('./only.js')\n\n/** @typedef {typeof import ('./context') & {\n *  app: Application\n *  req: import('http').IncomingMessage\n *  res: import('http').ServerResponse\n *  request: KoaRequest\n *  response: KoaResponse\n *  state: any\n *  originalUrl: string\n * }} Context */\n\n/** @typedef {typeof import('./request')} KoaRequest */\n\n/** @typedef {typeof import('./response')} KoaResponse */\n\n/**\n * Expose `Application` class.\n * Inherits from `Emitter.prototype`.\n */\n\nmodule.exports = class Application extends Emitter {\n  /**\n   * Initialize a new `Application`.\n   *\n   * @api public\n   */\n\n  /**\n   *\n   * @param {object} [options] Application options\n   * @param {string} [options.env='development'] Environment\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   * @param {function} [options.compose] Function to handle middleware composition\n   * @param {boolean} [options.asyncLocalStorage] Enable AsyncLocalStorage, default to false\n   *\n   */\n\n  constructor (options) {\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    this.compose = options.compose || compose\n    if (options.keys) this.keys = options.keys\n    this.middleware = []\n    this.context = Object.create(context)\n    this.request = Object.create(request)\n    this.response = Object.create(response)\n    // util.inspect.custom support for node 6+\n    /* istanbul ignore else */\n    if (util.inspect.custom) {\n      this[util.inspect.custom] = this.inspect\n    }\n    if (options.asyncLocalStorage) {\n      if (options.asyncLocalStorage instanceof AsyncLocalStorage) {\n        this.ctxStorage = options.asyncLocalStorage\n      } else {\n        this.ctxStorage = new AsyncLocalStorage()\n      }\n    }\n  }\n\n  /**\n   * Shorthand for:\n   *\n   *    http.createServer(app.callback()).listen(...)\n   *\n   * @param {Mixed} ...\n   * @return {import('http').Server}\n   * @api public\n   */\n\n  listen (...args) {\n    debug('listen')\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   * @return {Object}\n   * @api public\n   */\n\n  toJSON () {\n    return only(this, ['subdomainOffset', 'proxy', 'env'])\n  }\n\n  /**\n   * Inspect implementation.\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  inspect () {\n    return this.toJSON()\n  }\n\n  /**\n   * Use the given middleware `fn`.\n   *\n   * Old-style middleware will be converted.\n   *\n   * @param {(context: Context) => Promise<any | void>} fn\n   * @return {Application} self\n   * @api public\n   */\n\n  use (fn) {\n    if (typeof fn !== 'function') { throw new TypeError('middleware must be a function!') }\n    debug('use %s', fn._name || fn.name || '-')\n    this.middleware.push(fn)\n    return this\n  }\n\n  /**\n   * Return a request handler callback\n   * for node's native http server.\n   *\n   * @return {Function}\n   * @api public\n   */\n\n  callback () {\n    const fn = this.compose(this.middleware)\n\n    if (!this.listenerCount('error')) this.on('error', this.onerror)\n\n    const handleRequest = (req, res) => {\n      const ctx = this.createContext(req, res)\n      if (!this.ctxStorage) {\n        return this.handleRequest(ctx, fn)\n      }\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 () {\n    if (this.ctxStorage) return this.ctxStorage.getStore()\n  }\n\n  /**\n   * Handle request in callback.\n   *\n   * @api private\n   */\n\n  handleRequest (ctx, fnMiddleware) {\n    const res = ctx.res\n    res.statusCode = 404\n    const onerror = (err) => ctx.onerror(err)\n    const handleResponse = () => respond(ctx)\n    onFinished(res, onerror)\n    return fnMiddleware(ctx).then(handleResponse).catch(onerror)\n  }\n\n  /**\n   * Initialize a new context.\n   *\n   * @api private\n   */\n\n  createContext (req, res) {\n    /** @type {Context} */\n    const context = Object.create(this.context)\n    /** @type {KoaRequest} */\n    const request = (context.request = Object.create(this.request))\n    /** @type {KoaResponse} */\n    const response = (context.response = Object.create(this.response))\n    context.app = request.app = response.app = this\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.originalUrl = request.originalUrl = req.url\n    context.state = {}\n    return context\n  }\n\n  /**\n   * Default error handler.\n   *\n   * @param {Error} err\n   * @api private\n   */\n\n  onerror (err) {\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 =\n      Object.prototype.toString.call(err) === '[object Error]' ||\n      err instanceof 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    console.error(`\\n${msg.replace(/^/gm, '  ')}\\n`)\n  }\n\n  /**\n   * Help TS users comply to CommonJS, ESM, bundler mismatch.\n   * @see https://github.com/koajs/koa/issues/1513\n   */\n\n  static get default () {\n    return Application\n  }\n}\n\n/**\n * Response helper.\n */\n\nfunction respond (ctx) {\n  // allow bypassing koa\n  if (ctx.respond === false) return\n\n  const res = ctx.res\n\n  if (!ctx.writable) return res.end()\n\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    return res.end()\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    return res.end()\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      ctx.length = 0\n      return res.end()\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    return res.end(body)\n  }\n\n  // responses\n\n  if (Buffer.isBuffer(body)) return res.end(body)\n  if (typeof body === 'string') return res.end(body)\n\n  let stream = null\n  if (body instanceof Blob) stream = Stream.Readable.from(body.stream())\n  else if (body instanceof ReadableStream) stream = Stream.Readable.from(body)\n  else if (body instanceof Response) stream = Stream.Readable.from(body?.body || '')\n  else if (isStream(body)) stream = body\n\n  if (stream) {\n    return Stream.pipeline(stream, res, err => {\n      if (err && ctx.app.listenerCount('error')) ctx.onerror(err)\n    })\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/**\n * Make HttpError available to consumers of the library so that consumers don't\n * have a direct dependency upon `http-errors`\n */\n\nmodule.exports.HttpError = HttpError\n"
  },
  {
    "path": "lib/context.js",
    "content": "'use strict'\n\n/**\n * Module dependencies.\n */\n\nconst util = require('util')\nconst createError = require('http-errors')\nconst httpAssert = require('http-assert')\nconst delegate = require('delegates')\nconst statuses = require('statuses')\nconst Cookies = require('cookies')\n\nconst COOKIES = Symbol('context#cookies')\n\n/**\n * Context prototype.\n */\n\nconst proto = module.exports = {\n\n  /**\n   * util.inspect() implementation, which\n   * just returns the JSON output.\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  inspect () {\n    if (this === proto) return this\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   * @return {Object}\n   * @api public\n   */\n\n  toJSON () {\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   *    this.assert(this.user, 401, 'Please login!');\n   *\n   * See: https://github.com/jshttp/http-assert\n   *\n   * @param {Mixed} test\n   * @param {Number} status\n   * @param {String} message\n   * @api public\n   */\n\n  assert: httpAssert,\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   *\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} err, msg or status\n   * @param {String|Number|Error} [err, msg or status]\n   * @param {Object} [props]\n   * @api public\n   */\n\n  throw (...args) {\n    throw createError(...args)\n  },\n\n  /**\n   * Default error handling.\n   *\n   * @param {Error} err\n   * @api private\n   */\n\n  onerror (err) {\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) 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 =\n      Object.prototype.toString.call(err) === '[object Error]' ||\n      err instanceof Error\n    if (!isNativeError) err = new Error(util.format('non-error thrown: %j', err))\n\n    let headerSent = false\n    if (this.headerSent || !this.writable) {\n      headerSent = 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\n\n    // first unset all headers\n    /* istanbul ignore else */\n    if (typeof res.getHeaderNames === 'function') {\n      res.getHeaderNames().forEach(name => res.removeHeader(name))\n    } else {\n      res._headers = {} // Node < 7.7\n    }\n\n    // then set those specified\n    this.set(err.headers)\n\n    // force text/plain\n    this.type = 'text'\n\n    let statusCode = err.status || err.statusCode\n\n    // default to 500\n    if (typeof statusCode !== 'number' || !statuses.message[statusCode]) statusCode = 500\n\n    // respond\n    const code = statuses.message[statusCode]\n    const msg = err.expose ? err.message : code\n    this.status = err.status = statusCode\n    this.length = Buffer.byteLength(msg)\n    res.end(msg)\n  },\n\n  get cookies () {\n    if (!this[COOKIES]) {\n      this[COOKIES] = new Cookies(this.req, this.res, {\n        keys: this.app.keys,\n        secure: this.request.secure\n      })\n    }\n    return this[COOKIES]\n  },\n\n  set cookies (_cookies) {\n    this[COOKIES] = _cookies\n  }\n}\n\n/**\n * Custom inspection implementation for newer Node.js versions.\n *\n * @return {Object}\n * @api public\n */\n\n/* istanbul ignore else */\nif (util.inspect.custom) {\n  module.exports[util.inspect.custom] = module.exports.inspect\n}\n\n/**\n * Response delegation.\n */\n\ndelegate(proto, 'response')\n  .method('attachment')\n  .method('redirect')\n  .method('remove')\n  .method('vary')\n  .method('has')\n  .method('set')\n  .method('append')\n  .method('flushHeaders')\n  .method('back')\n  .access('status')\n  .access('message')\n  .access('body')\n  .access('length')\n  .access('type')\n  .access('lastModified')\n  .access('etag')\n  .getter('headerSent')\n  .getter('writable')\n\n/**\n * Request delegation.\n */\n\ndelegate(proto, 'request')\n  .method('acceptsLanguages')\n  .method('acceptsEncodings')\n  .method('acceptsCharsets')\n  .method('accepts')\n  .method('get')\n  .method('is')\n  .access('querystring')\n  .access('idempotent')\n  .access('socket')\n  .access('search')\n  .access('method')\n  .access('query')\n  .access('path')\n  .access('url')\n  .access('accept')\n  .getter('origin')\n  .getter('href')\n  .getter('subdomains')\n  .getter('protocol')\n  .getter('host')\n  .getter('hostname')\n  .getter('URL')\n  .getter('header')\n  .getter('headers')\n  .getter('secure')\n  .getter('stale')\n  .getter('fresh')\n  .getter('ips')\n  .getter('ip')\n"
  },
  {
    "path": "lib/is-stream.js",
    "content": "'use strict'\n\nconst Stream = require('stream')\n\n// TODO: use a third party library for this\n\nmodule.exports = (stream) => {\n  return (\n    stream instanceof Stream ||\n    (stream !== null &&\n      typeof stream === 'object' &&\n      !!stream.readable &&\n      typeof stream.pipe === 'function' &&\n      typeof stream.read === 'function' &&\n      typeof stream.readable === 'boolean' &&\n      typeof stream.readableObjectMode === 'boolean' &&\n      typeof stream.destroy === 'function' &&\n      typeof stream.destroyed === 'boolean')\n  )\n}\n"
  },
  {
    "path": "lib/only.js",
    "content": "module.exports = (obj, keys) => {\n  const ret = {}\n  for (let i = 0; i < keys.length; i++) {\n    const key = keys[i]\n    if (obj[key] == null) continue\n    ret[key] = obj[key]\n  }\n  return ret\n}\n"
  },
  {
    "path": "lib/request.js",
    "content": "'use strict'\n\n/**\n * Module dependencies.\n */\n\nconst URL = require('url').URL\nconst net = require('net')\nconst accepts = require('accepts')\nconst contentType = require('content-type')\nconst stringify = require('url').format\nconst parse = require('parseurl')\nconst sp = require('./search-params.js')\n\nconst typeis = require('type-is')\nconst fresh = require('fresh')\nconst only = require('./only.js')\nconst util = require('util')\n\nconst IP = Symbol('context#ip')\n\n/**\n * Prototype.\n */\n\nmodule.exports = {\n\n  /**\n   * Return request header.\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  get header () {\n    return this.req.headers\n  },\n\n  /**\n   * Set request header.\n   *\n   * @api public\n   */\n\n  set header (val) {\n    this.req.headers = val\n  },\n\n  /**\n   * Return request header, alias as request.header\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  get headers () {\n    return this.req.headers\n  },\n\n  /**\n   * Set request header, alias as request.header\n   *\n   * @api public\n   */\n\n  set headers (val) {\n    this.req.headers = val\n  },\n\n  /**\n   * Get request URL.\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get url () {\n    return this.req.url\n  },\n\n  /**\n   * Set request URL.\n   *\n   * @api public\n   */\n\n  set url (val) {\n    this.req.url = val\n  },\n\n  /**\n   * Get the origin header.\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get origin () {\n    return this.req.headers.origin || null\n  },\n\n  /**\n   * Get full request URL.\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get href () {\n    // support: `GET http://example.com/foo`\n    if (/^https?:\\/\\//i.test(this.originalUrl)) return this.originalUrl\n    return this.protocol + '://' + this.host + this.originalUrl\n  },\n\n  /**\n   * Get request method.\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get method () {\n    return this.req.method\n  },\n\n  /**\n   * Set request method.\n   *\n   * @param {String} val\n   * @api public\n   */\n\n  set method (val) {\n    this.req.method = val\n  },\n\n  /**\n   * Get request pathname.\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get path () {\n    return parse(this.req).pathname\n  },\n\n  /**\n   * Set pathname, retaining the query string when present.\n   *\n   * @param {String} path\n   * @api public\n   */\n\n  set path (path) {\n    const url = parse(this.req)\n    if (url.pathname === path) return\n\n    url.pathname = path\n    url.path = null\n\n    this.url = stringify(url)\n  },\n\n  /**\n   * Get parsed query string.\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  get query () {\n    const str = this.querystring\n    const c = this._querycache = this._querycache || {}\n    return c[str] || (c[str] = sp.parse(str))\n  },\n\n  /**\n   * Set query string as an object.\n   *\n   * @param {Object} obj\n   * @api public\n   */\n\n  set query (obj) {\n    this.querystring = sp.stringify(obj)\n  },\n\n  /**\n   * Get query string.\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get querystring () {\n    if (!this.req) return ''\n    return parse(this.req).query || ''\n  },\n\n  /**\n   * Set query string.\n   *\n   * @param {String} str\n   * @api public\n   */\n\n  set querystring (str) {\n    const url = parse(this.req)\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   * @return {String}\n   * @api public\n   */\n\n  get search () {\n    if (!this.querystring) return ''\n    return `?${this.querystring}`\n  },\n\n  /**\n   * Set the search string. Same as\n   * request.querystring= but included for ubiquity.\n   *\n   * @param {String} str\n   * @api public\n   */\n\n  set search (str) {\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   *\n   * @return {String} hostname:port\n   * @api public\n   */\n\n  get host () {\n    const proxy = this.app.proxy\n    let host = proxy && this.get('X-Forwarded-Host')\n    if (!host) {\n      if (this.req.httpVersionMajor >= 2) host = this.get(':authority')\n      if (!host) host = this.get('Host')\n    }\n    if (!host) return ''\n    host = splitCommaSeparatedValues(host, 1)[0]\n    // Host header may contain userinfo (e.g., \"user@host\") which is invalid per RFC 7230.\n    // Use URL parser to correctly extract the host portion.\n    if (host.includes('@')) {\n      try {\n        host = new URL(`http://${host}`).host\n      } catch (e) {\n        return ''\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   * @return {String} hostname\n   * @api public\n   */\n\n  get hostname () {\n    const host = this.host\n    if (!host) return ''\n    if (host[0] === '[') return this.URL.hostname || '' // IPv6\n    return host.split(':', 1)[0]\n  },\n\n  /**\n   * Get WHATWG parsed URL.\n   * Lazily memoized.\n   *\n   * @return {URL|Object}\n   * @api public\n   */\n\n  get URL () {\n    /* istanbul ignore else */\n    if (!this.memoizedURL) {\n      const originalUrl = this.originalUrl || '' // avoid undefined in template string\n      try {\n        this.memoizedURL = new URL(`${this.protocol}://${this.host}${originalUrl}`)\n      } catch (err) {\n        this.memoizedURL = Object.create(null)\n      }\n    }\n    return this.memoizedURL\n  },\n\n  /**\n   * Check if the request is fresh, aka\n   * Last-Modified and/or the ETag\n   * still match.\n   *\n   * @return {Boolean}\n   * @api public\n   */\n\n  get fresh () {\n    const method = this.method\n    const s = this.ctx.status\n\n    // GET or HEAD for weak freshness validation only\n    if (method !== 'GET' && method !== 'HEAD') return false\n\n    // 2xx or 304 as per rfc2616 14.26\n    if ((s >= 200 && s < 300) || s === 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   * @return {Boolean}\n   * @api public\n   */\n\n  get stale () {\n    return !this.fresh\n  },\n\n  /**\n   * Check if the request is idempotent.\n   *\n   * @return {Boolean}\n   * @api public\n   */\n\n  get idempotent () {\n    const methods = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']\n    return !!~methods.indexOf(this.method)\n  },\n\n  /**\n   * Return the request socket.\n   *\n   * @return {Connection}\n   * @api public\n   */\n\n  get socket () {\n    return this.req.socket\n  },\n\n  /**\n   * Get the charset when present or undefined.\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get charset () {\n    try {\n      const { parameters } = contentType.parse(this.req)\n      return parameters.charset || ''\n    } catch (e) {\n      return ''\n    }\n  },\n\n  /**\n   * Return parsed Content-Length when present.\n   *\n   * @return {Number|void}\n   * @api public\n   */\n\n  get length () {\n    const len = this.get('Content-Length')\n    if (len === '') return\n    return ~~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   * @return {String}\n   * @api public\n   */\n\n  get protocol () {\n    if (this.socket.encrypted) return 'https'\n    if (!this.app.proxy) return 'http'\n    const proto = this.get('X-Forwarded-Proto')\n    return proto ? splitCommaSeparatedValues(proto, 1)[0] : 'http'\n  },\n\n  /**\n   * Shorthand for:\n   *\n   *    this.protocol == 'https'\n   *\n   * @return {Boolean}\n   * @api public\n   */\n\n  get secure () {\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   * @return {Array}\n   * @api public\n   */\n\n  get ips () {\n    const proxy = this.app.proxy\n    const val = this.get(this.app.proxyIpHeader)\n    let ips = proxy && val\n      ? splitCommaSeparatedValues(val)\n      : []\n    if (this.app.maxIpsCount > 0) {\n      ips = ips.slice(-this.app.maxIpsCount)\n    }\n    return ips\n  },\n\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   * @return {String}\n   * @api public\n   */\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) {\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   * @return {Array}\n   * @api public\n   */\n\n  get subdomains () {\n    const offset = this.app.subdomainOffset\n    const hostname = this.hostname\n    if (net.isIP(hostname)) return []\n    return hostname\n      .split('.')\n      .reverse()\n      .slice(offset)\n  },\n\n  /**\n   * Get accept object.\n   * Lazily memoized.\n   *\n   * @return {Object}\n   * @api private\n   */\n\n  get accept () {\n    return this._accept || (this._accept = accepts(this.req))\n  },\n\n  /**\n   * Set accept object.\n   *\n   * @param {Object} obj\n   * @api private\n   */\n\n  set accept (obj) {\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   * @param {String|Array} type(s)...\n   * @return {String|Array|false}\n   * @api public\n   */\n\n  accepts (...args) {\n    return this.accept.types(...args)\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   * @param {String|Array} encoding(s)...\n   * @return {String|Array}\n   * @api public\n   */\n\n  acceptsEncodings (...args) {\n    return this.accept.encodings(...args)\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   * @param {String|Array} charset(s)...\n   * @return {String|Array}\n   * @api public\n   */\n\n  acceptsCharsets (...args) {\n    return this.accept.charsets(...args)\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   * @param {String|Array} lang(s)...\n   * @return {Array|String}\n   * @api public\n   */\n\n  acceptsLanguages (...args) {\n    return this.accept.languages(...args)\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   * @param {String|String[]} [type]\n   * @param {String[]} [types]\n   * @return {String|false|null}\n   * @api public\n   */\n\n  is (type, ...types) {\n    return typeis(this.req, type, ...types)\n  },\n\n  /**\n   * Return the request mime type void of\n   * parameters such as \"charset\".\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get type () {\n    const type = this.get('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   * @param {String} field\n   * @return {String}\n   * @api public\n   */\n\n  get (field) {\n    const req = this.req\n    switch (field = field.toLowerCase()) {\n      case 'referer':\n      case 'referrer':\n        return req.headers.referrer || req.headers.referer || ''\n      default:\n        return req.headers[field] || ''\n    }\n  },\n\n  /**\n   * Inspect implementation.\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  inspect () {\n    if (!this.req) return\n    return this.toJSON()\n  },\n\n  /**\n   * Return JSON representation.\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  toJSON () {\n    return only(this, [\n      'method',\n      'url',\n      'header'\n    ])\n  }\n}\n\n/**\n * Custom inspection implementation for newer Node.js versions.\n *\n * @return {Object}\n * @api public\n */\n\n/* istanbul ignore else */\nif (util.inspect.custom) {\n  module.exports[util.inspect.custom] = module.exports.inspect\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.\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, limit) {\n  return value.split(',', limit).map(v => v.trim())\n}\n"
  },
  {
    "path": "lib/response.js",
    "content": "'use strict'\n\n/**\n * Module dependencies.\n */\n\nconst assert = require('node:assert')\nconst extname = require('node:path').extname\nconst util = require('node:util')\n\nconst contentDisposition = require('content-disposition')\nconst onFinish = require('on-finished')\nconst escape = require('escape-html')\nconst typeis = require('type-is').is\nconst statuses = require('statuses')\nconst destroy = require('destroy')\nconst encodeUrl = require('encodeurl')\nconst vary = require('vary')\nconst getType = require('mime-types').contentType\n\nconst isStream = require('./is-stream.js')\nconst only = require('./only.js')\n\n/**\n * Prototype.\n */\n\nmodule.exports = {\n\n  /**\n   * Return the request socket.\n   *\n   * @return {Connection}\n   * @api public\n   */\n\n  get socket () {\n    return this.res.socket\n  },\n\n  /**\n   * Return response header.\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  get header () {\n    const { res } = this\n    return typeof res.getHeaders === 'function'\n      ? res.getHeaders()\n      : res._headers || {} // Node < 7.7\n  },\n\n  /**\n   * Return response header, alias as response.header\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  get headers () {\n    return this.header\n  },\n\n  /**\n   * Get response status code.\n   *\n   * @return {Number}\n   * @api public\n   */\n\n  get status () {\n    return this.res.statusCode\n  },\n\n  /**\n   * Set response status code.\n   *\n   * @param {Number} code\n   * @api public\n   */\n\n  set status (code) {\n    if (this.headerSent) return\n\n    assert(Number.isInteger(code), 'status code must be a number')\n    assert(code >= 100 && code <= 999, `invalid status code: ${code}`)\n    this._explicitStatus = true\n    this.res.statusCode = code\n    if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses.message[code]\n    if (this.body && statuses.empty[code]) this.body = null\n  },\n\n  /**\n   * Get response status message\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get message () {\n    return this.res.statusMessage || statuses.message[this.status]\n  },\n\n  /**\n   * Set response status message\n   *\n   * @param {String} msg\n   * @api public\n   */\n\n  set message (msg) {\n    this.res.statusMessage = msg\n  },\n\n  /**\n   * Get response body.\n   *\n   * @return {Mixed}\n   * @api public\n   */\n\n  get body () {\n    return this._body\n  },\n\n  /**\n   * Set response body.\n   *\n   * @param {String|Buffer|Object|Stream|ReadableStream|Blob|Response} val\n   * @api public\n   */\n\n  set body (val) {\n    const original = this._body\n    this._body = val\n\n    const cleanupPreviousStream = () => {\n      if (original && isStream(original)) {\n        original.once('error', () => {})\n        // Only destroy if the new value is not a stream\n        if (!isStream(val)) {\n          destroy(original)\n        }\n      }\n    }\n\n    // no content\n\n    if (val == null) {\n      if (!statuses.empty[this.status]) {\n        if (this.type === 'application/json') {\n          this._body = 'null'\n          return\n        }\n        this.status = 204\n      }\n      if (val === null) this._explicitNullBody = true\n      this.remove('Content-Type')\n      this.remove('Content-Length')\n      this.remove('Transfer-Encoding')\n      cleanupPreviousStream()\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      cleanupPreviousStream()\n      return\n    }\n\n    // buffer\n    if (Buffer.isBuffer(val)) {\n      if (setType) this.type = 'bin'\n      this.length = val.length\n      cleanupPreviousStream()\n      return\n    }\n\n    // stream\n    if (isStream(val)) {\n      onFinish(this.res, destroy.bind(null, val))\n      if (original !== val) {\n        if (original != null) this.remove('Content-Length')\n        cleanupPreviousStream()\n      }\n\n      if (setType) this.type = 'bin'\n      return\n    }\n\n    // ReadableStream\n    if (val instanceof ReadableStream) {\n      if (setType) this.type = 'bin'\n      cleanupPreviousStream()\n      return\n    }\n\n    // blob\n    if (val instanceof Blob) {\n      if (setType) this.type = 'bin'\n      this.length = val.size\n      cleanupPreviousStream()\n      return\n    }\n\n    // Response\n    if (val instanceof Response) {\n      this.status = val.status\n      if (setType) this.type = 'bin'\n      const headers = val.headers\n      for (const key of headers.keys()) {\n        this.set(key, headers.get(key))\n      }\n      cleanupPreviousStream()\n      return\n    }\n\n    // json\n    this.remove('Content-Length')\n    if (!this.type || !/\\bjson\\b/i.test(this.type)) this.type = 'json'\n    cleanupPreviousStream()\n  },\n\n  /**\n   * Set Content-Length field to `n`.\n   *\n   * @param {Number} n\n   * @api public\n   */\n\n  set length (n) {\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   * @return {Number}\n   * @api public\n   */\n\n  get length () {\n    if (this.has('Content-Length')) {\n      return parseInt(this.get('Content-Length'), 10) || 0\n    }\n\n    const { body } = this\n    if (!body || isStream(body)) return undefined\n    if (typeof body === 'string') return Buffer.byteLength(body)\n    if (Buffer.isBuffer(body)) return body.length\n    return Buffer.byteLength(JSON.stringify(body))\n  },\n\n  /**\n   * Check if a header has been written to the socket.\n   *\n   * @return {Boolean}\n   * @api public\n   */\n\n  get headerSent () {\n    return this.res.headersSent\n  },\n\n  /**\n   * Vary on `field`.\n   *\n   * @param {String|String[]} field\n   * @api public\n   */\n\n  vary (field) {\n    if (this.headerSent) return\n\n    vary(this.res, field)\n  },\n\n  /**\n   * Perform a 302 redirect to `url`.\n   *\n   * Examples:\n   *\n   *    this.redirect('/login');\n   *    this.redirect('http://google.com');\n   *\n   * @param {String} url\n   * @api public\n   */\n\n  redirect (url) {\n    if (/^https?:\\/\\//i.test(url)) {\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   * Perform a special-cased \"back\" to provide Referrer support.\n   * When Referrer is not present, `alt` or \"/\" is used.\n   *\n   * Examples:\n   *\n   *    ctx.back()\n   *    ctx.back('/index.html')\n   *\n   * @param {String} [alt]\n   * @api public\n   */\n\n  back (alt) {\n    const referrer = this.ctx.get('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        this.redirect(referrer)\n        return\n      }\n    }\n\n    // no referrer, use alt or '/'\n    this.redirect(alt || '/')\n  },\n\n  /**\n   * Set Content-Disposition header to \"attachment\" with optional `filename`.\n   *\n   * @param {String} [filename]\n   * @param {object} [options]\n   * @param {string} [options.type=attachment]\n   * @param {string|boolean} [options.fallback=true]\n   * @api public\n   */\n\n  attachment (filename, options) {\n    if (filename && !this.has('Content-Type')) {\n      this.type = extname(filename)\n    }\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   * @param {String} type\n   * @api public\n   */\n\n  set type (type) {\n    type = getType(type)\n    if (type) {\n      this.set('Content-Type', type)\n    } else {\n      this.remove('Content-Type')\n    }\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   * @param {String|Date} val\n   * @api public\n   */\n\n  set lastModified (val) {\n    if (typeof val === 'string') val = new Date(val)\n    this.set('Last-Modified', val.toUTCString())\n  },\n\n  /**\n   * Get the Last-Modified date in Date form, if it exists.\n   *\n   * @return {Date}\n   * @api public\n   */\n\n  get lastModified () {\n    const date = this.get('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 = 'md5hashsum';\n   *     this.response.etag = '\"md5hashsum\"';\n   *     this.response.etag = 'W/\"123456789\"';\n   *\n   * @param {String} val\n   * @api public\n   */\n\n  set etag (val) {\n    if (!/^(W\\/)?\"/.test(val)) val = `\"${val}\"`\n    this.set('ETag', val)\n  },\n\n  /**\n   * Get the ETag of a response.\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get etag () {\n    return this.get('ETag')\n  },\n\n  /**\n   * Return the response mime type void of\n   * parameters such as \"charset\".\n   *\n   * @return {String}\n   * @api public\n   */\n\n  get type () {\n    const type = this.get('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   * @param {String|String[]} [type]\n   * @param {String[]} [types]\n   * @return {String|false}\n   * @api public\n   */\n\n  is (type, ...types) {\n    return typeis(this.type, type, ...types)\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   * @param {String} field\n   * @return {any}\n   * @api public\n   */\n\n  get (field) {\n    return this.res.getHeader(field)\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   * @param {String} field\n   * @return {boolean}\n   * @api public\n   */\n\n  has (field) {\n    return typeof this.res.hasHeader === 'function'\n      ? this.res.hasHeader(field)\n      // Node < 7.7\n      : field.toLowerCase() in this.headers\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   * @param {String|{ [k: string]: any }} field\n   * @param {any} [val]\n   * @api public\n   */\n\n  set (field, val) {\n    if (this.headerSent || !field) return\n\n    if (typeof field === 'string') {\n      this.res.setHeader(field, val)\n    } else {\n      Object.keys(field).forEach(header => this.res.setHeader(header, field[header]))\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   *\n   * @param {String} field\n   * @param {*} val\n   * @api public\n   */\n\n  append (field, val) {\n    const prev = this.get(field)\n\n    if (prev) {\n      val = Array.isArray(prev)\n        ? prev.concat(val)\n        : [prev].concat(val)\n    }\n\n    return this.set(field, val)\n  },\n\n  /**\n   * Remove header `field`.\n   *\n   * @param {String} field\n   * @api public\n   */\n\n  remove (field) {\n    if (this.headerSent) return\n\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   * @return {Boolean}\n   * @api private\n   */\n\n  get writable () {\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   * @return {Object}\n   * @api public\n   */\n\n  inspect () {\n    if (!this.res) return\n    const o = this.toJSON()\n    o.body = this.body\n    return o\n  },\n\n  /**\n   * Return JSON representation.\n   *\n   * @return {Object}\n   * @api public\n   */\n\n  toJSON () {\n    return only(this, [\n      'status',\n      'message',\n      'header'\n    ])\n  },\n\n  /**\n   * Flush any set headers and begin the body\n   */\n\n  flushHeaders () {\n    this.res.flushHeaders()\n  }\n}\n\n/**\n * Custom inspection implementation for node 6+.\n *\n * @return {Object}\n * @api public\n */\n\n/* istanbul ignore else */\nif (util.inspect.custom) {\n  module.exports[util.inspect.custom] = module.exports.inspect\n}\n"
  },
  {
    "path": "lib/search-params.js",
    "content": "const URLSearchParams = require('url').URLSearchParams\n\nmodule.exports = {\n  stringify: (obj) => {\n    const searchParams = new URLSearchParams()\n    const addKey = (k, v, params) => {\n      const val = typeof v === 'string' || typeof v === 'number' ? v : ''\n      params.append(k, val)\n    }\n\n    for (const [key, value] of Object.entries(obj)) {\n      if (Array.isArray(value)) {\n        const lgth = value.length\n        for (let i = 0; i < lgth; i++) {\n          addKey(key, value[i], searchParams)\n        }\n      } else {\n        addKey(key, value, searchParams)\n      }\n    }\n    return searchParams.toString()\n  },\n\n  parse: (str) => {\n    const searchParams = new URLSearchParams(str)\n    const obj = {}\n    for (const key of searchParams.keys()) {\n      const values = searchParams.getAll(key)\n      obj[key] = values.length <= 1 ? values[0] : values\n    }\n    return obj\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"koa\",\n  \"version\": \"3.1.2\",\n  \"description\": \"Koa web app framework\",\n  \"main\": \"lib/application.js\",\n  \"exports\": {\n    \".\": {\n      \"require\": \"./lib/application.js\",\n      \"import\": \"./dist/koa.mjs\",\n      \"default\": \"./dist/koa.mjs\"\n    },\n    \"./*\": \"./*.js\",\n    \"./*.js\": \"./*.js\",\n    \"./package\": \"./package.json\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"test\": \"node --test\",\n    \"test:coverage\": \"c8 --reporter=lcov --reporter=text-summary node --test\",\n    \"lint\": \"standard\",\n    \"lint:fix\": \"standard --fix\",\n    \"lint:pretty\": \"standard | snazzy\",\n    \"authors\": \"git log --format='%aN <%aE>' | sort -u > AUTHORS\",\n    \"build\": \"gen-esm-wrapper . ./dist/koa.mjs\",\n    \"prepare\": \"npm run build\"\n  },\n  \"repository\": \"koajs/koa\",\n  \"keywords\": [\n    \"web\",\n    \"app\",\n    \"http\",\n    \"application\",\n    \"framework\",\n    \"middleware\",\n    \"rack\"\n  ],\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"accepts\": \"^1.3.8\",\n    \"content-disposition\": \"~1.0.1\",\n    \"content-type\": \"^1.0.5\",\n    \"cookies\": \"~0.9.1\",\n    \"delegates\": \"^1.0.0\",\n    \"destroy\": \"^1.2.0\",\n    \"encodeurl\": \"^2.0.0\",\n    \"escape-html\": \"^1.0.3\",\n    \"fresh\": \"~0.5.2\",\n    \"http-assert\": \"^1.5.0\",\n    \"http-errors\": \"^2.0.0\",\n    \"koa-compose\": \"^4.1.0\",\n    \"mime-types\": \"^3.0.1\",\n    \"on-finished\": \"^2.4.1\",\n    \"parseurl\": \"^1.3.3\",\n    \"statuses\": \"^2.0.1\",\n    \"type-is\": \"^2.0.1\",\n    \"vary\": \"^1.1.2\"\n  },\n  \"devDependencies\": {\n    \"c8\": \"^10.1.3\",\n    \"gen-esm-wrapper\": \"^1.1.3\",\n    \"snazzy\": \"^9.0.0\",\n    \"standard\": \"^17.1.2\",\n    \"supertest\": \"^7.1.1\"\n  },\n  \"engines\": {\n    \"node\": \">= 18\"\n  },\n  \"files\": [\n    \"dist\",\n    \"lib\"\n  ],\n  \"homepage\": \"https://koajs.com\"\n}\n"
  },
  {
    "path": "test-helpers/context.js",
    "content": "'use strict'\n\nconst Stream = require('stream')\nconst Koa = require('../lib/application')\n\nmodule.exports = (req, res, app) => {\n  const socket = new Stream.Duplex()\n  req = Object.assign({ headers: {}, socket }, Stream.Readable.prototype, req)\n  res = Object.assign({ _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 => res._headers[k.toLowerCase()]\n  res.setHeader = (k, v) => { res._headers[k.toLowerCase()] = v }\n  res.removeHeader = (k, v) => delete res._headers[k.toLowerCase()]\n  return app.createContext(req, res)\n}\n\nmodule.exports.request = (req, res, app) => module.exports(req, res, app).request\n\nmodule.exports.response = (req, res, app) => module.exports(req, res, app).response\n"
  },
  {
    "path": "test-helpers/stream.js",
    "content": "'use strict'\n\nconst { EventEmitter } = require('events')\n\nclass Readable extends EventEmitter {\n  pipe () {}\n  read () {}\n  destroy () {}\n  get readable () {\n    return true\n  }\n\n  get readableObjectMode () {\n    return false\n  }\n\n  get destroyed () {\n    return false\n  }\n}\n\nmodule.exports = {\n  Readable\n}\n"
  }
]