From e435b97e09783de62801374a1fb365c553ed4833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Louren=C3=A7o?= Date: Sun, 1 Dec 2024 17:08:43 -0300 Subject: [PATCH] feat(express-v5): added support to express v5 and body-parser v2 --- README.md | 2 +- package-lock.json | 1081 ++++++++++++++--- package.json | 6 +- src/network/request.ts | 5 +- .../body-parser-v2.framework.spec.ts | 10 + .../body-parser.framework.helper.ts | 531 ++++++++ test/frameworks/body-parser.framework.spec.ts | 492 +------- test/frameworks/express-v5.framework.spec.ts | 35 + test/handlers/aws-stream.handler.spec.ts | 4 +- test/network/request.spec.ts | 2 +- www/docs/main/frameworks/express.mdx | 2 +- 11 files changed, 1486 insertions(+), 684 deletions(-) create mode 100644 test/frameworks/body-parser-v2.framework.spec.ts create mode 100644 test/frameworks/body-parser.framework.helper.ts create mode 100644 test/frameworks/express-v5.framework.spec.ts diff --git a/README.md b/README.md index 8fa42446..023dc0c1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ [![Code Coverage][codecov-img]][codecov-url] [![Commitizen Friendly][commitizen-img]][commitizen-url] -Run REST APIs and other web applications using your existing Node.js application framework (NestJS, Deepkit, Express, Koa, Hapi, +Run REST APIs and other web applications using your existing Node.js application framework (NestJS, Deepkit, Express (v4 and v5), Koa, Hapi, Fastify, tRPC and Apollo Server), on top of AWS Lambda, Azure, Digital Ocean and many other clouds. This library was a refactored version of [@vendia/serverless-express](https://github.com/vendia/serverless-express), I diff --git a/package-lock.json b/package-lock.json index e08b62c0..a91bfec5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,8 @@ "@typescript-eslint/eslint-plugin": "6.15.0", "@typescript-eslint/parser": "6.15.0", "@vitest/coverage-v8": "1.1.3", - "body-parser": "1.20.2", + "body-parser": "1.20.3", + "body-parser-v2": "npm:body-parser@2", "commitizen": "4.3.0", "cors": "2.8.5", "cz-conventional-changelog": "3.3.0", @@ -46,7 +47,8 @@ "eslint-plugin-node": "11.1.0", "eslint-plugin-prettier": "5.1.2", "eslint-plugin-tsdoc": "0.2.17", - "express": "4.18.2", + "express": "4.21.1", + "express-v5": "npm:express@5", "fastify": "4.25.2", "firebase-admin": "11.11.1", "firebase-functions": "4.5.0", @@ -87,7 +89,7 @@ "firebase-functions": ">= 4.0.0", "http-errors": ">= 2.0.0", "koa": ">= 2.5.1", - "reflect-metadata": "^0.1.13" + "reflect-metadata": ">= 0.1.13" }, "peerDependenciesMeta": { "@apollo/server": { @@ -2379,14 +2381,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@hapi/mimos/node_modules/mime-db": { - "version": "1.52.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@hapi/nigel": { "version": "5.0.1", "dev": true, @@ -4825,6 +4819,8 @@ }, "node_modules/array-flatten": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true, "license": "MIT" }, @@ -5087,7 +5083,9 @@ "optional": true }, "node_modules/body-parser": { - "version": "1.20.2", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "license": "MIT", "dependencies": { @@ -5099,7 +5097,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -5109,47 +5107,105 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", + "node_modules/body-parser-v2": { + "name": "body-parser", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.2.tgz", + "integrity": "sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "3.1.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.5.2", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "^3.0.0", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">=18" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", + "node_modules/body-parser-v2/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", + "node_modules/body-parser-v2/node_modules/iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "side-channel": "^1.0.4" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.5.2", + "node_modules/body-parser-v2/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser-v2/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.6.3", "unpipe": "1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/body-parser-v2/node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -5295,14 +5351,20 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, + "license": "MIT", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5787,9 +5849,14 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookiejar": { "version": "2.1.4", @@ -6004,11 +6071,13 @@ } }, "node_modules/debug": { - "version": "4.3.4", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -6019,6 +6088,13 @@ } } }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/dedent": { "version": "0.7.0", "dev": true, @@ -6197,17 +6273,21 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -6525,6 +6605,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", @@ -7253,36 +7356,38 @@ } }, "node_modules/express": { - "version": "4.18.2", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -7293,156 +7398,633 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", + "node_modules/express-v5": { + "name": "express", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", + "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", "content-type": "~1.0.4", - "debug": "2.6.9", + "cookie": "0.7.1", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", "depd": "2.0.0", - "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "merge-descriptors": "^2.0.0", + "methods": "~1.1.2", + "mime-types": "^3.0.0", "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "once": "1.4.0", + "parseurl": "~1.3.3", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "router": "^2.0.0", + "safe-buffer": "5.2.1", + "send": "^1.1.0", + "serve-static": "^2.1.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "^2.0.0", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">= 18" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", + "node_modules/express-v5/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", + "node_modules/express-v5/node_modules/body-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.2.tgz", + "integrity": "sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "side-channel": "^1.0.4" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "3.1.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.5.2", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "^3.0.0", + "type-is": "~1.6.18" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", + "node_modules/express-v5/node_modules/body-parser/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } }, - "node_modules/extend": { - "version": "3.0.2", + "node_modules/express-v5/node_modules/body-parser/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">= 0.6" + } }, - "node_modules/external-editor": { - "version": "3.1.0", + "node_modules/express-v5/node_modules/body-parser/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "mime-db": "1.52.0" }, "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/faker": { - "version": "5.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-content-type-parse": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", - "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", - "dev": true - }, - "node_modules/fast-decode-uri-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", + "node_modules/express-v5/node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.2.0", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/express-v5/node_modules/body-parser/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { - "node": ">=8.6.0" + "node": ">= 0.6" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", + "node_modules/express-v5/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "safe-buffer": "5.2.1" }, "engines": { - "node": ">= 6" + "node": ">= 0.6" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", + "node_modules/express-v5/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/fast-json-stringify": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.9.1.tgz", + "node_modules/express-v5/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express-v5/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-v5/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-v5/node_modules/iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express-v5/node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-v5/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-v5/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-v5/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-v5/node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express-v5/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/express-v5/node_modules/send": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/express-v5/node_modules/send/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-v5/node_modules/send/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-v5/node_modules/send/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-v5/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/express-v5/node_modules/type-is": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", + "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-v5/node_modules/type-is/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/express/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/faker": { + "version": "5.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "dev": true + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.9.1.tgz", "integrity": "sha512-NMrf+uU9UJnTzfxaumMDXK1NWqtPCfGoM9DYIE+ESlaTQqjlANFBy0VAbsm6FB88Mx0nceyi18zTo5kIEUlzxg==", "dev": true, "dependencies": { @@ -7580,7 +8162,9 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", + "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7598,6 +8182,8 @@ }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { @@ -7606,6 +8192,8 @@ }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, @@ -7958,16 +8546,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, + "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8363,12 +8956,13 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9022,6 +9616,13 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -9894,9 +10495,17 @@ "license": "MIT" }, "node_modules/merge-descriptors": { - "version": "1.0.1", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -9953,7 +10562,9 @@ } }, "node_modules/mime-db": { - "version": "1.51.0", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", "engines": { @@ -9961,11 +10572,13 @@ } }, "node_modules/mime-types": { - "version": "2.1.34", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -10597,7 +11210,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "dev": true, "license": "MIT" }, @@ -11122,11 +11737,13 @@ } }, "node_modules/qs": { - "version": "6.11.1", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -11169,7 +11786,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "license": "MIT", "dependencies": { @@ -11532,6 +12151,42 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "node_modules/router": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", + "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-flatten": "3.0.0", + "is-promise": "4.0.0", + "methods": "~1.1.2", + "parseurl": "~1.3.3", + "path-to-regexp": "^8.0.0", + "setprototypeof": "1.2.0", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/router/node_modules/array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/run-applescript": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", @@ -11737,17 +12392,60 @@ "license": "MIT" }, "node_modules/serve-static": { - "version": "1.15.0", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", + "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-static/node_modules/send": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" } }, "node_modules/set-cookie-parser": { @@ -11757,15 +12455,18 @@ "dev": true }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, + "license": "MIT", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11831,13 +12532,19 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" diff --git a/package.json b/package.json index b1780ef4..c2917e27 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,8 @@ "@typescript-eslint/eslint-plugin": "6.15.0", "@typescript-eslint/parser": "6.15.0", "@vitest/coverage-v8": "1.1.3", - "body-parser": "1.20.2", + "body-parser": "1.20.3", + "body-parser-v2": "npm:body-parser@2", "commitizen": "4.3.0", "cors": "2.8.5", "cz-conventional-changelog": "3.3.0", @@ -115,7 +116,8 @@ "eslint-plugin-node": "11.1.0", "eslint-plugin-prettier": "5.1.2", "eslint-plugin-tsdoc": "0.2.17", - "express": "4.18.2", + "express": "4.21.1", + "express-v5": "npm:express@5", "fastify": "4.25.2", "firebase-admin": "11.11.1", "firebase-functions": "4.5.0", diff --git a/src/network/request.ts b/src/network/request.ts index b437c9ab..40bfbc6d 100644 --- a/src/network/request.ts +++ b/src/network/request.ts @@ -55,9 +55,12 @@ export class ServerlessRequest extends IncomingMessage { }: ServerlessRequestProps) { super({ encrypted: true, - readable: false, + readable: true, // credits to @pnkp at https://github.com/CodeGenieApp/serverless-express/pull/692 remoteAddress, address: () => ({ port: HTTPS_PORT }) as AddressInfo, + on: NO_OP, + removeListener: NO_OP, + removeEventListener: NO_OP, end: NO_OP, destroy: NO_OP, } as any); diff --git a/test/frameworks/body-parser-v2.framework.spec.ts b/test/frameworks/body-parser-v2.framework.spec.ts new file mode 100644 index 00000000..689cea62 --- /dev/null +++ b/test/frameworks/body-parser-v2.framework.spec.ts @@ -0,0 +1,10 @@ +import { describe, vitest } from 'vitest'; +import { createBodyParserTests } from './body-parser.framework.helper'; + +vitest.mock('body-parser', async () => { + return await import('body-parser-v2'); +}); + +describe('Body Parser v2', () => { + createBodyParserTests(); +}); diff --git a/test/frameworks/body-parser.framework.helper.ts b/test/frameworks/body-parser.framework.helper.ts new file mode 100644 index 00000000..0566cbcb --- /dev/null +++ b/test/frameworks/body-parser.framework.helper.ts @@ -0,0 +1,531 @@ +import * as trpc from '@trpc/server'; +import type { Options } from 'body-parser'; +import express from 'express'; +import express_v5, { type Express } from 'express'; +import fastify from 'fastify'; +import Application from 'koa'; +import polka from 'polka'; +import { type SpyInstance, describe, expect, it, vitest } from 'vitest'; +import { + type FrameworkContract, + ServerlessRequest, + ServerlessResponse, + waitForStreamComplete, +} from '../../src'; +import { + type BodyParserOptions, + JsonBodyParserFramework, + RawBodyParserFramework, + TextBodyParserFramework, + UrlencodedBodyParserFramework, +} from '../../src/frameworks/body-parser'; +import { ExpressFramework } from '../../src/frameworks/express'; +import { FastifyFramework } from '../../src/frameworks/fastify'; +import { setNoOpForContentType } from '../../src/frameworks/fastify/helpers/no-op-content-parser'; +import { KoaFramework } from '../../src/frameworks/koa'; +import { PolkaFramework } from '../../src/frameworks/polka'; +import { + type TrpcAdapterContext, + TrpcFramework, +} from '../../src/frameworks/trpc'; + +type BodyParserTest = { + name: string; + createFramework: ( + framework: FrameworkContract, + ) => FrameworkContract; + body: Buffer; + contentType: string; + expectedBody?: any; + notExpectedBody?: any; + status: number; + expectSendRequestOfTheFrameworkToBeCalled: boolean; + skipFrameworks?: ( + | 'express' + | 'fastify' + | 'koa' + | 'hapi' + | 'trpc' + | 'polka' + )[]; +}; + +const bodyParserOptions: BodyParserTest[] = [ + { + name: 'json: default behavior', + createFramework: framework => framework, + body: Buffer.from(JSON.stringify({ message: 'ok' }), 'utf-8'), + contentType: 'application/json', + expectedBody: Buffer.from(JSON.stringify({ message: 'ok' }), 'utf-8'), + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + }, + { + name: 'json: parse successfuly json', + createFramework: framework => new JsonBodyParserFramework(framework), + body: Buffer.from(JSON.stringify({ message: 'ok' }), 'utf-8'), + contentType: 'application/json', + expectedBody: { message: 'ok' }, + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + }, + { + name: 'json: error on parse, limit', + createFramework: framework => + new JsonBodyParserFramework(framework, { limit: 4 }), + body: Buffer.from(JSON.stringify({ message: 'ok' }), 'utf-8'), + contentType: 'application/json', + notExpectedBody: JSON.stringify({ message: 'ok' }), + status: 413, + expectSendRequestOfTheFrameworkToBeCalled: false, + }, + { + name: 'json: error on parse, invalid json', + createFramework: framework => new JsonBodyParserFramework(framework), + body: Buffer.from('{"potato":true', 'utf-8'), + contentType: 'application/json', + notExpectedBody: '{"potato":true', + status: 400, + expectSendRequestOfTheFrameworkToBeCalled: false, + }, + { + name: 'json: error on parse, invalid json and strict syntax', + createFramework: framework => + new JsonBodyParserFramework(framework, { strict: true }), + body: Buffer.from('"potato":true}', 'utf-8'), + contentType: 'application/json', + notExpectedBody: '{"potato":true', + status: 400, + expectSendRequestOfTheFrameworkToBeCalled: false, + }, + { + name: 'text: default behavior', + createFramework: framework => framework, + body: Buffer.from('potato=cool', 'utf-8'), + contentType: 'text/plain', + expectedBody: Buffer.from('potato=cool', 'utf-8'), + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + }, + { + name: 'text: parse text', + createFramework: framework => new TextBodyParserFramework(framework), + body: Buffer.from('potato=cool', 'utf-8'), + contentType: 'text/plain', + expectedBody: 'potato=cool', + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + skipFrameworks: ['trpc'], + }, + { + name: 'text: error on size limit', + createFramework: framework => + new TextBodyParserFramework(framework, { limit: 4 }), + body: Buffer.from('potato=cool', 'utf-8'), + contentType: 'text/plain', + notExpectedBody: 'potato=cool', + status: 413, + expectSendRequestOfTheFrameworkToBeCalled: false, + }, + { + name: 'raw: default behavior', + createFramework: framework => framework, + body: Buffer.from('potato=cool', 'utf-8'), + contentType: 'application/octet-stream', + expectedBody: Buffer.from('potato=cool', 'utf-8'), + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + }, + { + name: 'raw: parse raw successfuly', + createFramework: framework => new RawBodyParserFramework(framework), + body: Buffer.from('potato=cool', 'utf-8'), + contentType: 'application/octet-stream', + expectedBody: Buffer.from('potato=cool', 'utf-8'), + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + }, + { + name: 'raw: error on size limit', + createFramework: framework => + new RawBodyParserFramework(framework, { limit: 4 }), + body: Buffer.from('potato=cool', 'utf-8'), + contentType: 'application/octet-stream', + notExpectedBody: Buffer.from('potato=cool', 'utf-8'), + status: 413, + expectSendRequestOfTheFrameworkToBeCalled: false, + }, + { + name: 'urlencoded: default behavior', + createFramework: framework => framework, + body: Buffer.from('foo=bar', 'utf-8'), + contentType: 'application/x-www-form-urlencoded', + expectedBody: Buffer.from('foo=bar', 'utf-8'), + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + }, + { + name: 'urlencoded: parse urlencoded', + createFramework: framework => new UrlencodedBodyParserFramework(framework), + body: Buffer.from('foo=bar', 'utf-8'), + contentType: 'application/x-www-form-urlencoded', + expectedBody: { foo: 'bar' }, + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + }, + { + name: 'urlencoded: parse urlencoded extended', + createFramework: framework => + new UrlencodedBodyParserFramework(framework, { extended: true }), + body: Buffer.from('foo[bar]=test', 'utf-8'), + contentType: 'application/x-www-form-urlencoded', + expectedBody: { foo: { bar: 'test' } }, + status: 200, + expectSendRequestOfTheFrameworkToBeCalled: true, + }, + { + name: 'urlencoded: error on max size', + createFramework: framework => + new UrlencodedBodyParserFramework(framework, { limit: 3 }), + body: Buffer.from('foo=bar', 'utf-8'), + contentType: 'application/x-www-form-urlencoded', + notExpectedBody: { foo: 'bar' }, + status: 413, + expectSendRequestOfTheFrameworkToBeCalled: false, + }, +]; + +function createFramework( + options: BodyParserTest, + instance: FrameworkContract, +): [ + FrameworkContract, + SpyInstance['sendRequest']>>, +] { + const spy = vitest.spyOn(instance, 'sendRequest'); + + return [options.createFramework(instance), spy]; +} + +function createRequest(body: Buffer, contentType: string): ServerlessRequest { + return new ServerlessRequest({ + method: 'POST', + url: '/body', + body, + headers: { + 'content-type': contentType, + 'content-length': Buffer.byteLength(body, 'utf-8').toString(), + accept: contentType, + }, + }); +} + +function createResponse(method: string): ServerlessResponse { + return new ServerlessResponse({ + method, + }); +} + +async function handleRestExpects( + app: TApp, + framework: FrameworkContract, + bodyParserTestOptions: BodyParserTest, +): Promise { + const [bodyParserFramework, spySendRequest] = createFramework( + bodyParserTestOptions, + framework, + ); + + const request = createRequest( + bodyParserTestOptions.body, + bodyParserTestOptions.contentType, + ); + const response = createResponse(request.method!); + + bodyParserFramework.sendRequest(app, request, response); + + await waitForStreamComplete(response); + + const returnedBody = ServerlessResponse.body(response); + + expect( + response.statusCode, + `Got status ${response.statusCode} instead of ${ + bodyParserTestOptions.status + }. Response Body: ${returnedBody.toString()}`, + ).toEqual(bodyParserTestOptions.status); + + if (bodyParserTestOptions.expectSendRequestOfTheFrameworkToBeCalled) + expect(spySendRequest).toHaveBeenCalled(); + else expect(spySendRequest).not.toHaveBeenCalled(); +} + +export function createBodyParserTests() { + describe('BodyParserFramework', () => { + describe('express', () => { + for (const bodyParserTest of bodyParserOptions) { + const itFn = bodyParserTest?.skipFrameworks?.includes('express') + ? it.skip + : it; + + itFn(bodyParserTest.name, async () => { + const app = express(); + + app.post('/body', (req, res) => { + if (bodyParserTest.expectedBody) + expect(req.body).toEqual(bodyParserTest.expectedBody); + else expect(req.body).not.toEqual(bodyParserTest.notExpectedBody); + + res.send('ok'); + }); + + app.use((err, __, res, _) => { + res.emit('error', err); + }); + + await handleRestExpects(app, new ExpressFramework(), bodyParserTest); + }); + } + }); + + describe('express-v5', () => { + for (const bodyParserTest of bodyParserOptions) { + const itFn = bodyParserTest?.skipFrameworks?.includes('express') + ? it.skip + : it; + + itFn(bodyParserTest.name, async () => { + const app = express_v5(); + + app.post('/body', (req, res) => { + if (bodyParserTest.expectedBody) + expect(req.body).toEqual(bodyParserTest.expectedBody); + else expect(req.body).not.toEqual(bodyParserTest.notExpectedBody); + + res.send('ok'); + }); + + app.use((err, __, res, _) => { + res.emit('error', err); + }); + + await handleRestExpects(app, new ExpressFramework(), bodyParserTest); + }); + } + }); + + describe('fastify', () => { + for (const bodyParserTest of bodyParserOptions) { + const itFn = bodyParserTest?.skipFrameworks?.includes('fastify') + ? it.skip + : it; + + itFn(bodyParserTest.name, async () => { + const app = fastify(); + + setNoOpForContentType(app, 'application/json'); + setNoOpForContentType(app, 'text/plain'); + setNoOpForContentType(app, 'application/octet-stream'); + setNoOpForContentType(app, 'application/x-www-form-urlencoded'); + + app.post('/body', (req, res) => { + if (bodyParserTest.expectedBody) + expect(req.body).toEqual(bodyParserTest.expectedBody); + else expect(req.body).not.toEqual(bodyParserTest.notExpectedBody); + + res.send('ok'); + }); + + app.setErrorHandler((err, _req, reply) => { + reply.raw.emit('error', err); + }); + + await handleRestExpects(app, new FastifyFramework(), bodyParserTest); + }); + } + }); + + describe('koa', () => { + for (const bodyParserTest of bodyParserOptions) { + const itFn = bodyParserTest?.skipFrameworks?.includes('koa') + ? it.skip + : it; + + itFn(bodyParserTest.name, async () => { + const app = new Application(); + + app.onerror = e => { + throw e; + }; + + const next = vitest.fn(ctx => { + const body = ctx.req.body; + + if (bodyParserTest.expectedBody) + expect(body).toEqual(bodyParserTest.expectedBody); + else expect(body).not.toEqual(bodyParserTest.notExpectedBody); + + ctx.status = 200; + ctx.body = 'ok'; + }); + + app.use(next); + + await handleRestExpects(app, new KoaFramework(), bodyParserTest); + }); + } + }); + + describe('hapi', () => { + for (const bodyParserTest of bodyParserOptions) { + const itFn = bodyParserTest?.skipFrameworks?.includes('hapi') + ? it.skip + : it; + + itFn(bodyParserTest.name, async () => { + const app = new Application(); + + app.use(ctx => { + const body = (ctx.req as any).body; + + if (bodyParserTest.expectedBody) + expect(body).toEqual(bodyParserTest.expectedBody); + else expect(body).not.toEqual(bodyParserTest.notExpectedBody); + + ctx.status = 200; + ctx.body = 'ok'; + }); + + app.onerror = e => { + throw e; + }; + + await handleRestExpects(app, new KoaFramework(), bodyParserTest); + }); + } + }); + + describe('trpc', () => { + for (const bodyParserTest of bodyParserOptions) { + const itFn = bodyParserTest?.skipFrameworks?.includes('trpc') + ? it.skip + : it; + + itFn(bodyParserTest.name, async () => { + const t = trpc.initTRPC + .context>() + .create(); + + const app = t.router({ + body: t.procedure + .input(inp => inp) + .mutation(ctx => { + const body = (ctx.ctx.request as any).body; + + if (bodyParserTest.expectedBody) + expect(body).toEqual(bodyParserTest.expectedBody); + else expect(body).not.toEqual(bodyParserTest.notExpectedBody); + + return 'ok'; + }), + }); + + await handleRestExpects(app, new TrpcFramework(), bodyParserTest); + }); + } + }); + + describe('polka', () => { + for (const bodyParserTest of bodyParserOptions) { + const itFn = bodyParserTest?.skipFrameworks?.includes('polka') + ? it.skip + : it; + + itFn(bodyParserTest.name, async () => { + const app = polka(); + + app.post('/body', (req, res) => { + if (bodyParserTest.expectedBody) + expect(req.body).toEqual(bodyParserTest.expectedBody); + else expect(req.body).not.toEqual(bodyParserTest.notExpectedBody); + + res.end('ok'); + }); + + await handleRestExpects(app, new PolkaFramework(), bodyParserTest); + }); + } + }); + + it('should handle correctly on wrong content-encoding', async () => { + const app = express(); + + const expressFramework = new ExpressFramework(); + const bodyParserFramework = new TextBodyParserFramework(expressFramework); + + const request = createRequest( + Buffer.from('testrandomdata'), + 'text/plain', + ); + request.headers['content-encoding'] = 'random'; + + const response = createResponse('POST'); + + bodyParserFramework.sendRequest(app, request, response); + + await waitForStreamComplete(response); + + expect(response.statusCode).toEqual(415); + }); + + describe('customErrorHandler', () => { + it('should be able to set custom error handler', async () => { + const app = express(); + + const customOptions: Options & BodyParserOptions = { + limit: 4, + customErrorHandler: (__, response, _) => { + response.statusCode = 400; + response.end('ok'); + }, + }; + + const expressFramework = new ExpressFramework(); + const bodyParserFrameworks: [ + framework: FrameworkContract, + contentType: string, + ][] = [ + [ + new TextBodyParserFramework(expressFramework, customOptions), + 'text/plain', + ], + [ + new JsonBodyParserFramework(expressFramework, customOptions), + 'application/json', + ], + [ + new UrlencodedBodyParserFramework(expressFramework, customOptions), + 'application/x-www-form-urlencoded', + ], + ]; + + for (const [bodyParserFramework, contentType] of bodyParserFrameworks) { + const request = createRequest( + Buffer.from('testrandomdata'), + contentType, + ); + const response = createResponse('POST'); + + bodyParserFramework.sendRequest(app, request, response); + + await waitForStreamComplete(response); + + const result = ServerlessResponse.body(response); + + expect(result).toEqual(Buffer.from('ok')); + expect(response.statusCode).toEqual(400); + } + }); + }); + }); +} diff --git a/test/frameworks/body-parser.framework.spec.ts b/test/frameworks/body-parser.framework.spec.ts index 785fd97c..edd9ed92 100644 --- a/test/frameworks/body-parser.framework.spec.ts +++ b/test/frameworks/body-parser.framework.spec.ts @@ -1,490 +1,6 @@ -import * as trpc from '@trpc/server'; -import type { Options } from 'body-parser'; -import express, { type Express } from 'express'; -import fastify from 'fastify'; -import Application from 'koa'; -import { type SpyInstance, describe, expect, it, vitest } from 'vitest'; -import polka from 'polka'; -import { - type FrameworkContract, - ServerlessRequest, - ServerlessResponse, - waitForStreamComplete, -} from '../../src'; -import { - type BodyParserOptions, - JsonBodyParserFramework, - RawBodyParserFramework, - TextBodyParserFramework, - UrlencodedBodyParserFramework, -} from '../../src/frameworks/body-parser'; -import { ExpressFramework } from '../../src/frameworks/express'; -import { FastifyFramework } from '../../src/frameworks/fastify'; -import { setNoOpForContentType } from '../../src/frameworks/fastify/helpers/no-op-content-parser'; -import { KoaFramework } from '../../src/frameworks/koa'; -import { - type TrpcAdapterContext, - TrpcFramework, -} from '../../src/frameworks/trpc'; -import { PolkaFramework } from '../../src/frameworks/polka'; +import { describe } from 'vitest'; +import { createBodyParserTests } from './body-parser.framework.helper'; -type BodyParserTest = { - name: string; - createFramework: ( - framework: FrameworkContract, - ) => FrameworkContract; - body: Buffer; - contentType: string; - expectedBody?: any; - notExpectedBody?: any; - status: number; - expectSendRequestOfTheFrameworkToBeCalled: boolean; - skipFrameworks?: ( - | 'express' - | 'fastify' - | 'koa' - | 'hapi' - | 'trpc' - | 'polka' - )[]; -}; - -const bodyParserOptions: BodyParserTest[] = [ - { - name: 'json: default behavior', - createFramework: framework => framework, - body: Buffer.from(JSON.stringify({ message: 'ok' }), 'utf-8'), - contentType: 'application/json', - expectedBody: Buffer.from(JSON.stringify({ message: 'ok' }), 'utf-8'), - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - }, - { - name: 'json: parse successfuly json', - createFramework: framework => new JsonBodyParserFramework(framework), - body: Buffer.from(JSON.stringify({ message: 'ok' }), 'utf-8'), - contentType: 'application/json', - expectedBody: { message: 'ok' }, - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - }, - { - name: 'json: error on parse, limit', - createFramework: framework => - new JsonBodyParserFramework(framework, { limit: 4 }), - body: Buffer.from(JSON.stringify({ message: 'ok' }), 'utf-8'), - contentType: 'application/json', - notExpectedBody: JSON.stringify({ message: 'ok' }), - status: 413, - expectSendRequestOfTheFrameworkToBeCalled: false, - }, - { - name: 'json: error on parse, invalid json', - createFramework: framework => new JsonBodyParserFramework(framework), - body: Buffer.from('{"potato":true', 'utf-8'), - contentType: 'application/json', - notExpectedBody: '{"potato":true', - status: 400, - expectSendRequestOfTheFrameworkToBeCalled: false, - }, - { - name: 'json: error on parse, invalid json and strict syntax', - createFramework: framework => - new JsonBodyParserFramework(framework, { strict: true }), - body: Buffer.from('"potato":true}', 'utf-8'), - contentType: 'application/json', - notExpectedBody: '{"potato":true', - status: 400, - expectSendRequestOfTheFrameworkToBeCalled: false, - }, - { - name: 'text: default behavior', - createFramework: framework => framework, - body: Buffer.from('potato=cool', 'utf-8'), - contentType: 'text/plain', - expectedBody: Buffer.from('potato=cool', 'utf-8'), - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - }, - { - name: 'text: parse text', - createFramework: framework => new TextBodyParserFramework(framework), - body: Buffer.from('potato=cool', 'utf-8'), - contentType: 'text/plain', - expectedBody: 'potato=cool', - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - skipFrameworks: ['trpc'], - }, - { - name: 'text: error on size limit', - createFramework: framework => - new TextBodyParserFramework(framework, { limit: 4 }), - body: Buffer.from('potato=cool', 'utf-8'), - contentType: 'text/plain', - notExpectedBody: 'potato=cool', - status: 413, - expectSendRequestOfTheFrameworkToBeCalled: false, - }, - { - name: 'raw: default behavior', - createFramework: framework => framework, - body: Buffer.from('potato=cool', 'utf-8'), - contentType: 'application/octet-stream', - expectedBody: Buffer.from('potato=cool', 'utf-8'), - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - }, - { - name: 'raw: parse raw successfuly', - createFramework: framework => new RawBodyParserFramework(framework), - body: Buffer.from('potato=cool', 'utf-8'), - contentType: 'application/octet-stream', - expectedBody: Buffer.from('potato=cool', 'utf-8'), - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - }, - { - name: 'raw: error on size limit', - createFramework: framework => - new RawBodyParserFramework(framework, { limit: 4 }), - body: Buffer.from('potato=cool', 'utf-8'), - contentType: 'application/octet-stream', - notExpectedBody: Buffer.from('potato=cool', 'utf-8'), - status: 413, - expectSendRequestOfTheFrameworkToBeCalled: false, - }, - { - name: 'urlencoded: default behavior', - createFramework: framework => framework, - body: Buffer.from('foo=bar', 'utf-8'), - contentType: 'application/x-www-form-urlencoded', - expectedBody: Buffer.from('foo=bar', 'utf-8'), - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - }, - { - name: 'urlencoded: parse urlencoded', - createFramework: framework => new UrlencodedBodyParserFramework(framework), - body: Buffer.from('foo=bar', 'utf-8'), - contentType: 'application/x-www-form-urlencoded', - expectedBody: { foo: 'bar' }, - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - }, - { - name: 'urlencoded: parse urlencoded extended', - createFramework: framework => - new UrlencodedBodyParserFramework(framework, { extended: true }), - body: Buffer.from('foo[bar]=test', 'utf-8'), - contentType: 'application/x-www-form-urlencoded', - expectedBody: { foo: { bar: 'test' } }, - status: 200, - expectSendRequestOfTheFrameworkToBeCalled: true, - }, - { - name: 'urlencoded: error on max size', - createFramework: framework => - new UrlencodedBodyParserFramework(framework, { limit: 3 }), - body: Buffer.from('foo=bar', 'utf-8'), - contentType: 'application/x-www-form-urlencoded', - notExpectedBody: { foo: 'bar' }, - status: 413, - expectSendRequestOfTheFrameworkToBeCalled: false, - }, -]; - -function createFramework( - options: BodyParserTest, - instance: FrameworkContract, -): [ - FrameworkContract, - SpyInstance['sendRequest']>>, -] { - const spy = vitest.spyOn(instance, 'sendRequest'); - - return [options.createFramework(instance), spy]; -} - -function createRequest(body: Buffer, contentType: string): ServerlessRequest { - return new ServerlessRequest({ - method: 'POST', - url: '/body', - body, - headers: { - 'content-type': contentType, - 'content-length': Buffer.byteLength(body, 'utf-8').toString(), - accept: contentType, - }, - }); -} - -function createResponse(method: string): ServerlessResponse { - return new ServerlessResponse({ - method, - }); -} - -async function handleRestExpects( - app: TApp, - framework: FrameworkContract, - bodyParserTestOptions: BodyParserTest, -): Promise { - const [bodyParserFramework, spySendRequest] = createFramework( - bodyParserTestOptions, - framework, - ); - - const request = createRequest( - bodyParserTestOptions.body, - bodyParserTestOptions.contentType, - ); - const response = createResponse(request.method!); - - bodyParserFramework.sendRequest(app, request, response); - - await waitForStreamComplete(response); - - expect(response.statusCode).toEqual(bodyParserTestOptions.status); - - if (bodyParserTestOptions.expectSendRequestOfTheFrameworkToBeCalled) - expect(spySendRequest).toHaveBeenCalled(); - else expect(spySendRequest).not.toHaveBeenCalled(); -} - -describe('BodyParserFramework', () => { - describe('express', () => { - for (const bodyParserTest of bodyParserOptions) { - const itFn = bodyParserTest?.skipFrameworks?.includes('express') - ? it.skip - : it; - - itFn(bodyParserTest.name, async () => { - const app = express(); - - app.post('/body', (req, res) => { - if (bodyParserTest.expectedBody) - expect(req.body).toEqual(bodyParserTest.expectedBody); - else expect(req.body).not.toEqual(bodyParserTest.notExpectedBody); - - res.send('ok'); - }); - - app.use((err, __, res, _) => { - res.emit('error', err); - }); - - await handleRestExpects(app, new ExpressFramework(), bodyParserTest); - }); - } - }); - - describe('fastify', () => { - for (const bodyParserTest of bodyParserOptions) { - const itFn = bodyParserTest?.skipFrameworks?.includes('fastify') - ? it.skip - : it; - - itFn(bodyParserTest.name, async () => { - const app = fastify(); - - setNoOpForContentType(app, 'application/json'); - setNoOpForContentType(app, 'text/plain'); - setNoOpForContentType(app, 'application/octet-stream'); - setNoOpForContentType(app, 'application/x-www-form-urlencoded'); - - app.post('/body', (req, res) => { - if (bodyParserTest.expectedBody) - expect(req.body).toEqual(bodyParserTest.expectedBody); - else expect(req.body).not.toEqual(bodyParserTest.notExpectedBody); - - res.send('ok'); - }); - - app.setErrorHandler((err, _req, reply) => { - reply.raw.emit('error', err); - }); - - await handleRestExpects(app, new FastifyFramework(), bodyParserTest); - }); - } - }); - - describe('koa', () => { - for (const bodyParserTest of bodyParserOptions) { - const itFn = bodyParserTest?.skipFrameworks?.includes('koa') - ? it.skip - : it; - - itFn(bodyParserTest.name, async () => { - const app = new Application(); - - app.onerror = e => { - throw e; - }; - - const next = vitest.fn(ctx => { - const body = ctx.req.body; - - if (bodyParserTest.expectedBody) - expect(body).toEqual(bodyParserTest.expectedBody); - else expect(body).not.toEqual(bodyParserTest.notExpectedBody); - - ctx.status = 200; - ctx.body = 'ok'; - }); - - app.use(next); - - await handleRestExpects(app, new KoaFramework(), bodyParserTest); - }); - } - }); - - describe('hapi', () => { - for (const bodyParserTest of bodyParserOptions) { - const itFn = bodyParserTest?.skipFrameworks?.includes('hapi') - ? it.skip - : it; - - itFn(bodyParserTest.name, async () => { - const app = new Application(); - - app.use(ctx => { - const body = (ctx.req as any).body; - - if (bodyParserTest.expectedBody) - expect(body).toEqual(bodyParserTest.expectedBody); - else expect(body).not.toEqual(bodyParserTest.notExpectedBody); - - ctx.status = 200; - ctx.body = 'ok'; - }); - - app.onerror = e => { - throw e; - }; - - await handleRestExpects(app, new KoaFramework(), bodyParserTest); - }); - } - }); - - describe('trpc', () => { - for (const bodyParserTest of bodyParserOptions) { - const itFn = bodyParserTest?.skipFrameworks?.includes('trpc') - ? it.skip - : it; - - itFn(bodyParserTest.name, async () => { - const t = trpc.initTRPC.context>().create(); - - const app = t.router({ - body: t.procedure - .input(inp => inp) - .mutation(ctx => { - const body = (ctx.ctx.request as any).body; - - if (bodyParserTest.expectedBody) - expect(body).toEqual(bodyParserTest.expectedBody); - else expect(body).not.toEqual(bodyParserTest.notExpectedBody); - - return 'ok'; - }), - }); - - await handleRestExpects(app, new TrpcFramework(), bodyParserTest); - }); - } - }); - - describe('polka', () => { - for (const bodyParserTest of bodyParserOptions) { - const itFn = bodyParserTest?.skipFrameworks?.includes('polka') - ? it.skip - : it; - - itFn(bodyParserTest.name, async () => { - const app = polka(); - - app.post('/body', (req, res) => { - if (bodyParserTest.expectedBody) - expect(req.body).toEqual(bodyParserTest.expectedBody); - else expect(req.body).not.toEqual(bodyParserTest.notExpectedBody); - - res.end('ok'); - }); - - await handleRestExpects(app, new PolkaFramework(), bodyParserTest); - }); - } - }); - - it('should handle correctly on wrong content-encoding', async () => { - const app = express(); - - const expressFramework = new ExpressFramework(); - const bodyParserFramework = new TextBodyParserFramework(expressFramework); - - const request = createRequest(Buffer.from('testrandomdata'), 'text/plain'); - request.headers['content-encoding'] = 'random'; - - const response = createResponse('POST'); - - bodyParserFramework.sendRequest(app, request, response); - - await waitForStreamComplete(response); - - expect(response.statusCode).toEqual(415); - }); - - describe('customErrorHandler', () => { - it('should be able to set custom error handler', async () => { - const app = express(); - - const customOptions: Options & BodyParserOptions = { - limit: 4, - customErrorHandler: (__, response, _) => { - response.statusCode = 400; - response.end('ok'); - }, - }; - - const expressFramework = new ExpressFramework(); - const bodyParserFrameworks: [ - framework: FrameworkContract, - contentType: string, - ][] = [ - [ - new TextBodyParserFramework(expressFramework, customOptions), - 'text/plain', - ], - [ - new JsonBodyParserFramework(expressFramework, customOptions), - 'application/json', - ], - [ - new UrlencodedBodyParserFramework(expressFramework, customOptions), - 'application/x-www-form-urlencoded', - ], - ]; - - for (const [bodyParserFramework, contentType] of bodyParserFrameworks) { - const request = createRequest( - Buffer.from('testrandomdata'), - contentType, - ); - const response = createResponse('POST'); - - bodyParserFramework.sendRequest(app, request, response); - - await waitForStreamComplete(response); - - const result = ServerlessResponse.body(response); - - expect(result).toEqual(Buffer.from('ok')); - expect(response.statusCode).toEqual(400); - } - }); - }); +describe('Body Parser v1', () => { + createBodyParserTests(); }); diff --git a/test/frameworks/express-v5.framework.spec.ts b/test/frameworks/express-v5.framework.spec.ts new file mode 100644 index 00000000..7d525dff --- /dev/null +++ b/test/frameworks/express-v5.framework.spec.ts @@ -0,0 +1,35 @@ +import express from 'express-v5'; +import { describe } from 'vitest'; +import { ExpressFramework } from '../../src/frameworks/express'; +import { type TestRouteBuilderHandler, createTestSuiteFor } from './utils'; + +function createHandler( + method: 'get' | 'post' | 'delete' | 'put', +): TestRouteBuilderHandler { + return (app, path, handler) => { + app[method](path, (request, response) => { + const [statusCode, resultBody, headers] = handler( + request.headers, + request.body, + ); + + for (const header of Object.keys(headers)) + response.header(header, headers[header]); + + response.status(statusCode).json(resultBody); + }); + }; +} + +describe(ExpressFramework.name, () => { + createTestSuiteFor( + () => new ExpressFramework(), + () => express(), + { + get: createHandler('get'), + delete: createHandler('delete'), + post: createHandler('post'), + put: createHandler('put'), + }, + ); +}); diff --git a/test/handlers/aws-stream.handler.spec.ts b/test/handlers/aws-stream.handler.spec.ts index 69f6dcdb..a0974f2f 100644 --- a/test/handlers/aws-stream.handler.spec.ts +++ b/test/handlers/aws-stream.handler.spec.ts @@ -229,9 +229,7 @@ describe('AwsStreamHandler', () => { const finalBuffer = Buffer.concat(writable.data); - expect(finalBuffer.toString()).toContain( - 'Redirecting to /test', - ); + expect(finalBuffer.toString()).toContain('Redirecting to /test'); }); } diff --git a/test/network/request.spec.ts b/test/network/request.spec.ts index d402e370..7a4daefb 100644 --- a/test/network/request.spec.ts +++ b/test/network/request.spec.ts @@ -28,7 +28,7 @@ describe('ServerlessRequest', () => { expect(request).toHaveProperty('httpVersionMajor', 1); expect(request).toHaveProperty('httpVersionMinor', 1); expect(request.socket).toHaveProperty('encrypted', true); - expect(request.socket).toHaveProperty('readable', false); + expect(request.socket).toHaveProperty('readable', true); expect(request.socket).toHaveProperty('remoteAddress', remoteAddress); expect(request.socket).toHaveProperty('end', NO_OP); expect(request.socket).toHaveProperty('destroy', NO_OP); diff --git a/www/docs/main/frameworks/express.mdx b/www/docs/main/frameworks/express.mdx index 26926e25..2a261b6f 100644 --- a/www/docs/main/frameworks/express.mdx +++ b/www/docs/main/frameworks/express.mdx @@ -1,5 +1,5 @@ --- -title: Express +title: Express (v4 and v5) description: See more about how to integrate with Express. ---