From bf70110b8e0d6fefedf8f432e45df60e077886a3 Mon Sep 17 00:00:00 2001 From: "xuping.wang" Date: Thu, 27 Aug 2026 23:57:48 +0800 Subject: [PATCH 1/2] chore: migrate koa-override into monorepo --- packages/egg/package.json | 2 +- .../egg/src/app/middleware/override_method.ts | 2 +- packages/koa-override/LICENSE | 24 ++++ packages/koa-override/README.md | 38 ++++++ packages/koa-override/package.json | 55 +++++++++ packages/koa-override/src/index.ts | 47 ++++++++ packages/koa-override/test/index.test.ts | 112 ++++++++++++++++++ packages/koa-override/tsconfig.json | 3 + packages/koa-override/tsdown.config.ts | 7 ++ packages/koa-override/vitest.config.ts | 3 + pnpm-workspace.yaml | 1 - tsconfig.json | 3 + wiki/index.md | 1 + wiki/log.md | 6 + wiki/packages/koa-override.md | 22 ++++ 15 files changed, 323 insertions(+), 3 deletions(-) create mode 100644 packages/koa-override/LICENSE create mode 100644 packages/koa-override/README.md create mode 100644 packages/koa-override/package.json create mode 100644 packages/koa-override/src/index.ts create mode 100644 packages/koa-override/test/index.test.ts create mode 100644 packages/koa-override/tsconfig.json create mode 100644 packages/koa-override/tsdown.config.ts create mode 100644 packages/koa-override/vitest.config.ts create mode 100644 wiki/packages/koa-override.md diff --git a/packages/egg/package.json b/packages/egg/package.json index 4efeddf03a..b365a00a3e 100644 --- a/packages/egg/package.json +++ b/packages/egg/package.json @@ -153,6 +153,7 @@ "@eggjs/extend2": "workspace:*", "@eggjs/i18n": "workspace:*", "@eggjs/jsonp": "workspace:*", + "@eggjs/koa-override": "workspace:*", "@eggjs/logrotator": "workspace:*", "@eggjs/multipart": "workspace:*", "@eggjs/onerror": "workspace:*", @@ -175,7 +176,6 @@ "humanize-ms": "catalog:", "is-type-of": "catalog:", "koa-bodyparser": "catalog:", - "koa-override": "catalog:", "onelogger": "catalog:", "performance-ms": "catalog:", "sendmessage": "catalog:", diff --git a/packages/egg/src/app/middleware/override_method.ts b/packages/egg/src/app/middleware/override_method.ts index e5b33b0458..3a80cfd3c2 100644 --- a/packages/egg/src/app/middleware/override_method.ts +++ b/packages/egg/src/app/middleware/override_method.ts @@ -1,3 +1,3 @@ -import override from 'koa-override'; +import override from '@eggjs/koa-override'; export default override; diff --git a/packages/koa-override/LICENSE b/packages/koa-override/LICENSE new file mode 100644 index 0000000000..d091f58c19 --- /dev/null +++ b/packages/koa-override/LICENSE @@ -0,0 +1,24 @@ +This software is licensed under the MIT License. + +Copyright (c) 2024-present eggjs and other contributors +Copyright (c) 2015-2024 node-modules and other contributors +Copyright (c) 2015 fengmk2 and other contributors +Copyright (c) 2014 Jonathan Ong + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/koa-override/README.md b/packages/koa-override/README.md new file mode 100644 index 0000000000..95c942a51d --- /dev/null +++ b/packages/koa-override/README.md @@ -0,0 +1,38 @@ +# @eggjs/koa-override + +Method override middleware for Koa. It lets clients use HTTP verbs such as +`PUT` or `DELETE` when they cannot send those methods directly. + +## Install + +```bash +npm install @eggjs/koa-override +``` + +## Usage + +```ts +import bodyParser from 'koa-bodyparser'; +import override from '@eggjs/koa-override'; + +app.use(bodyParser()); +app.use(override()); +``` + +## API + +### `override(options?)` + +When a request body exists, the middleware checks `body._method` first. It +otherwise checks the `X-HTTP-Method-Override` header. + +By default, only `POST` requests may be overridden. Use `allowedMethods` to +change that list: + +```ts +app.use(override({ allowedMethods: ['POST', 'PUT'] })); +``` + +## License + +[MIT](LICENSE) diff --git a/packages/koa-override/package.json b/packages/koa-override/package.json new file mode 100644 index 0000000000..3776f12032 --- /dev/null +++ b/packages/koa-override/package.json @@ -0,0 +1,55 @@ +{ + "name": "@eggjs/koa-override", + "version": "4.0.0-beta.26", + "description": "Method override middleware for Koa", + "keywords": [ + "koa", + "method-override", + "middleware", + "override", + "rewrite" + ], + "homepage": "https://github.com/eggjs/egg/tree/next/packages/koa-override", + "bugs": { + "url": "https://github.com/eggjs/egg/issues" + }, + "license": "MIT", + "author": "fengmk2 (https://github.com/fengmk2)", + "repository": { + "type": "git", + "url": "git+https://github.com/eggjs/egg.git", + "directory": "packages/koa-override" + }, + "files": [ + "dist" + ], + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public", + "exports": { + ".": "./dist/index.js", + "./package.json": "./package.json" + } + }, + "scripts": { + "typecheck": "tsgo --noEmit" + }, + "devDependencies": { + "@eggjs/koa": "workspace:*", + "@eggjs/supertest": "workspace:*", + "@eggjs/tsconfig": "workspace:*", + "@types/koa-bodyparser": "catalog:", + "koa-bodyparser": "catalog:", + "typescript": "catalog:" + }, + "engines": { + "node": ">=22.18.0" + } +} diff --git a/packages/koa-override/src/index.ts b/packages/koa-override/src/index.ts new file mode 100644 index 0000000000..43a2097435 --- /dev/null +++ b/packages/koa-override/src/index.ts @@ -0,0 +1,47 @@ +import { METHODS } from 'node:http'; + +import type { Context, MiddlewareFunc } from '@eggjs/koa'; + +const methods = METHODS.map((method) => method.toUpperCase()); + +export interface OverrideMiddlewareOptions { + /** Request methods that may be overridden. Defaults to `[ 'POST' ]`. */ + allowedMethods?: string[]; +} + +interface RequestWithBody { + method: string; + body?: { + _method?: unknown; + }; +} + +export default function override(options: OverrideMiddlewareOptions = {}): MiddlewareFunc { + const allowedMethods = options.allowedMethods ?? ['POST']; + + return function overrideMethod(ctx: Context, next): Promise { + const request = ctx.request as RequestWithBody; + if (!allowedMethods.includes(request.method)) { + return next(); + } + + let method: string | undefined; + if (typeof request.body?._method === 'string' && request.body._method) { + method = request.body._method.toUpperCase(); + } else { + const header = ctx.get('x-http-method-override'); + if (typeof header === 'string' && header) { + method = header.toUpperCase(); + } + } + + if (method) { + if (!methods.includes(method)) { + ctx.throw(400, `invalid override method: "${method}"`); + } + request.method = method; + } + + return next(); + }; +} diff --git a/packages/koa-override/test/index.test.ts b/packages/koa-override/test/index.test.ts new file mode 100644 index 0000000000..2adbddce89 --- /dev/null +++ b/packages/koa-override/test/index.test.ts @@ -0,0 +1,112 @@ +import { strict as assert } from 'node:assert'; + +import Koa, { type MiddlewareFunc } from '@eggjs/koa'; +import { request } from '@eggjs/supertest'; +import bodyParser from 'koa-bodyparser'; +import { describe, it } from 'vitest'; + +import override from '../src/index.ts'; + +describe('override method middleware', () => { + it('overrides with x-http-method-override header', async () => { + const app = new Koa(); + app.use(override()); + app.use((ctx) => { + ctx.body = { method: ctx.method, url: ctx.url }; + }); + + await request(app.callback()) + .post('/foo') + .set('X-Http-Method-Override', 'DELETE') + .expect({ method: 'DELETE', url: '/foo' }) + .expect(200); + }); + + it('overrides with body._method', async () => { + const app = new Koa(); + app.use((bodyParser as unknown as () => MiddlewareFunc)()); + app.use(override()); + app.use((ctx) => { + ctx.body = { method: ctx.method, url: ctx.url, body: ctx.request.body }; + }); + + await request(app.callback()) + .post('/foo') + .send({ _method: 'delete', value: 'koa' }) + .expect({ method: 'DELETE', url: '/foo', body: { _method: 'delete', value: 'koa' } }) + .expect(200); + }); + + it('ignores a non-string body._method', async () => { + const app = new Koa(); + app.use((bodyParser as unknown as () => MiddlewareFunc)()); + app.use(override()); + app.use((ctx) => { + ctx.body = { method: ctx.method, body: ctx.request.body }; + }); + + await request(app.callback()) + .post('/foo') + .send({ _method: 123 }) + .expect({ method: 'POST', body: { _method: 123 } }) + .expect(200); + }); + + it('falls back to the header when body._method is empty', async () => { + const app = new Koa(); + app.use((bodyParser as unknown as () => MiddlewareFunc)()); + app.use(override()); + app.use((ctx) => { + ctx.body = { method: ctx.method }; + }); + + await request(app.callback()) + .post('/foo') + .set('X-Http-Method-Override', 'DELETE') + .send({ _method: '' }) + .expect({ method: 'DELETE' }) + .expect(200); + }); + + it('rejects an invalid override method', async () => { + const app = new Koa(); + app.on('error', (err) => { + assert.equal(err.message, 'invalid override method: "SAVE"'); + }); + app.use(override()); + + await request(app.callback()) + .post('/foo') + .set('X-Http-Method-Override', 'SAVE') + .expect('invalid override method: "SAVE"') + .expect(400); + }); + + it('does not override a GET request by default', async () => { + const app = new Koa(); + app.use(override()); + app.use((ctx) => { + ctx.body = { method: ctx.method }; + }); + + await request(app.callback()) + .get('/foo') + .set('X-Http-Method-Override', 'DELETE') + .expect({ method: 'GET' }) + .expect(200); + }); + + it('supports custom allowedMethods', async () => { + const app = new Koa(); + app.use(override({ allowedMethods: ['POST', 'PUT'] })); + app.use((ctx) => { + ctx.body = { method: ctx.method }; + }); + + await request(app.callback()) + .put('/foo') + .set('X-Http-Method-Override', 'DELETE') + .expect({ method: 'DELETE' }) + .expect(200); + }); +}); diff --git a/packages/koa-override/tsconfig.json b/packages/koa-override/tsconfig.json new file mode 100644 index 0000000000..4082f16a5d --- /dev/null +++ b/packages/koa-override/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/koa-override/tsdown.config.ts b/packages/koa-override/tsdown.config.ts new file mode 100644 index 0000000000..6dfb63bdb8 --- /dev/null +++ b/packages/koa-override/tsdown.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: { + index: 'src/index.ts', + }, +}); diff --git a/packages/koa-override/vitest.config.ts b/packages/koa-override/vitest.config.ts new file mode 100644 index 0000000000..ebb0784a70 --- /dev/null +++ b/packages/koa-override/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({}); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8029106a4b..2f5383c1fc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -145,7 +145,6 @@ catalog: koa-bodyparser: ^4.4.1 koa-compose: ^4.1.0 koa-onerror: ^5.0.1 - koa-override: ^4.0.0 koa-range: ^0.3.0 koa-session: ^7.0.2 koa-static: ^5.0.0 diff --git a/tsconfig.json b/tsconfig.json index db00302e83..e9cac08639 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -53,6 +53,9 @@ { "path": "./packages/koa-static-cache" }, + { + "path": "./packages/koa-override" + }, { "path": "./packages/router" }, diff --git a/wiki/index.md b/wiki/index.md index 87c7e0d8f7..48a8cab0a3 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -27,6 +27,7 @@ Read this file before exploring raw sources. - [Core Package](./packages/core.md) - Loader, lifecycle, and application core primitives used by Egg runtime packages. - [Egg Bundler](./packages/egg-bundler.md) - Bundles Egg applications for Node startup snapshots and tegg standalone service workers. - [Loader FS Package](./packages/loader-fs.md) - Shared loader-facing filesystem boundary for Egg loaders and future bundled runtimes. +- [Koa Override Package](./packages/koa-override.md) - Method-override middleware used by Egg's default middleware stack. - [Onerror Plugin](./packages/onerror.md) - Default Egg error-handling plugin and configurable response negotiation layer. - [Standalone Service Worker](./packages/service-worker.md) - Fetch-semantics standalone runtime serving HTTP controllers and MCP tools from a tegg module without an egg application. - [Typings Package](./packages/typings.md) - Shared TypeScript type surface for cross-package Egg typings. diff --git a/wiki/log.md b/wiki/log.md index 6b14bffa48..67161107ee 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -2,6 +2,12 @@ Dates use the workspace-local Asia/Shanghai calendar date. +## [2026-08-27] package | migrate koa-override into the monorepo + +- sources touched: `packages/koa-override`, `packages/egg/src/app/middleware/override_method.ts`, `packages/egg/package.json` +- pages updated: `wiki/index.md`, `wiki/log.md`, `wiki/packages/koa-override.md` +- note: Added the scoped `@eggjs/koa-override` package with the original method-override behavior, TypeScript declarations, Vitest coverage, and Egg workspace consumption. + ## [2026-08-06] fix | restore standalone public dynamic injection - sources touched: `tegg/standalone/{standalone,service-worker-runtime}` diff --git a/wiki/packages/koa-override.md b/wiki/packages/koa-override.md new file mode 100644 index 0000000000..50647697d4 --- /dev/null +++ b/wiki/packages/koa-override.md @@ -0,0 +1,22 @@ +--- +title: Koa Override Package +type: package +summary: Method-override middleware used by Egg's default middleware stack. +source_files: + - packages/koa-override/src/index.ts + - packages/koa-override/package.json + - packages/egg/src/app/middleware/override_method.ts +updated_at: 2026-08-27 +status: active +--- + +# Koa Override Package + +`@eggjs/koa-override` provides the method-override middleware used by Egg's +default `overrideMethod` middleware. It accepts `_method` from a parsed request +body before checking the `X-HTTP-Method-Override` header, validates the target +against Node.js HTTP methods, and only permits `POST` requests by default. + +The package lives in the Egg monorepo so its TypeScript declarations, runtime +requirements, tests, and releases follow the Egg 4 toolchain. Egg consumes it +through a `workspace:*` dependency. From c134ee16d8ddb605d3deedef6e3d654cc177248b Mon Sep 17 00:00:00 2001 From: "xuping.wang" <2232455931@qq.com> Date: Fri, 28 Aug 2026 00:07:41 +0800 Subject: [PATCH 2/2] docs: clarify koa-override behavior and sources --- wiki/packages/koa-override.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/wiki/packages/koa-override.md b/wiki/packages/koa-override.md index 50647697d4..dc3fc65288 100644 --- a/wiki/packages/koa-override.md +++ b/wiki/packages/koa-override.md @@ -4,9 +4,14 @@ type: package summary: Method-override middleware used by Egg's default middleware stack. source_files: - packages/koa-override/src/index.ts + - packages/koa-override/test/index.test.ts - packages/koa-override/package.json + - packages/koa-override/tsconfig.json + - packages/koa-override/tsdown.config.ts + - packages/koa-override/vitest.config.ts + - packages/egg/package.json - packages/egg/src/app/middleware/override_method.ts -updated_at: 2026-08-27 +updated_at: 2026-08-28 status: active --- @@ -15,7 +20,9 @@ status: active `@eggjs/koa-override` provides the method-override middleware used by Egg's default `overrideMethod` middleware. It accepts `_method` from a parsed request body before checking the `X-HTTP-Method-Override` header, validates the target -against Node.js HTTP methods, and only permits `POST` requests by default. +against Node.js HTTP methods, and only processes overrides for `POST` requests +by default. Non-`POST` requests pass through unchanged unless callers configure +additional allowed methods. The package lives in the Egg monorepo so its TypeScript declarations, runtime requirements, tests, and releases follow the Egg 4 toolchain. Egg consumes it