From 79bccc514893884ebfaf1b6dba11d15fb0a30f59 Mon Sep 17 00:00:00 2001 From: Benjamin Kenawell Date: Mon, 4 May 2026 20:56:28 -0400 Subject: [PATCH 1/2] Extract request object from correct execution context Unfortunately, if using the decorator on a graphql resolver or operation, the request object lives in a slightly different place. This commit adds @nestjs/graphql as an optional peer dependency and adds support in the RequireFlagsEnabled decorator for the graphql context. Signed-off-by: Benjamin Kenawell --- package-lock.json | 415 +++++++++++++++--- packages/nest/package.json | 11 +- .../src/require-flags-enabled.decorator.ts | 4 +- packages/nest/src/utils.ts | 22 + 4 files changed, 393 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index 69a175ff2f..b59f3ff907 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1942,6 +1942,70 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@graphql-tools/merge": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.9.tgz", + "integrity": "sha512-iHUWNjRHeQRYdgIMIuChThOwoKzA9vrzYeslgfBo5eUYEyHGZCoDPjAavssoYXLwstYt1dZj2J22jSzc2DrN0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "10.0.33", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.33.tgz", + "integrity": "sha512-O6P3RIftO0jafnSsFAqpjurUuUxJ43s/AdPVLQsBkI6y4Ic/tKm4C1Qm1KKQsCDTOxXPJClh/v3g7k7yLKCFBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.9", + "@graphql-tools/utils": "^11.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.1.0.tgz", + "integrity": "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/@hono/node-server": { "version": "1.19.14", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", @@ -3357,6 +3421,95 @@ } } }, + "node_modules/@nestjs/graphql": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@nestjs/graphql/-/graphql-13.4.0.tgz", + "integrity": "sha512-NXZbC7ZGT4hoqA1M57tXz3Y+FgRgtEMCjyTU20bLr/Ck6itGL0NsrnudmLdxrc8XGLGM+JwmhQ606o1oRyW6jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "9.1.9", + "@graphql-tools/schema": "10.0.33", + "@graphql-tools/utils": "11.1.0", + "@nestjs/mapped-types": "2.1.1", + "chokidar": "4.0.3", + "fast-glob": "3.3.3", + "graphql-tag": "2.12.6", + "graphql-ws": "6.0.8", + "lodash": "4.18.1", + "normalize-path": "3.0.0", + "subscriptions-transport-ws": "0.11.0", + "tslib": "2.8.1", + "ws": "8.20.0" + }, + "peerDependencies": { + "@apollo/subgraph": "^2.9.3", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "class-transformer": "*", + "class-validator": "*", + "graphql": "^16.11.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "ts-morph": "^20.0.0 || ^21.0.0 || ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0" + }, + "peerDependenciesMeta": { + "@apollo/subgraph": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + }, + "ts-morph": { + "optional": true + } + } + }, + "node_modules/@nestjs/graphql/node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.1.tgz", + "integrity": "sha512-SCCoMEJ6jdeI5h/N+KCVF1+pmg/hmEkNA5nHTS8Gvww7T/LCl4o1gFLinw2iQ60w7slFkszHcGLKGdazVI4F8A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0 || ^0.15.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/platform-express": { "version": "11.1.19", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.19.tgz", @@ -4266,9 +4419,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4286,9 +4436,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4306,9 +4453,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4326,9 +4470,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4587,9 +4728,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4604,9 +4742,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4621,9 +4756,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4638,9 +4770,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4655,9 +4784,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4672,9 +4798,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4689,9 +4812,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4706,9 +4826,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4723,9 +4840,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4740,9 +4854,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4757,9 +4868,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4774,9 +4882,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4791,9 +4896,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6400,6 +6502,19 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "dev": true, @@ -6958,6 +7073,13 @@ "@babel/core": "^7.0.0" } }, + "node_modules/backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==", + "dev": true, + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "dev": true, @@ -7796,6 +7918,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -8318,6 +8453,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "prr": "~1.0.1" }, @@ -9180,7 +9316,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -9188,7 +9326,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -9739,6 +9877,60 @@ "dev": true, "license": "MIT" }, + "node_modules/graphql": { + "version": "16.13.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/graphql-ws": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.8.tgz", + "integrity": "sha512-m3EOaNsUBXwAnkBWbzPfe0Nq8pXUfxsWnolC54sru3FzHvhTZL0Ouf/BoQsaGAXqM+YPerXOJ47BUnmgmoupCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@fastify/websocket": "^10 || ^11", + "crossws": "~0.3", + "graphql": "^15.10.1 || ^16", + "ws": "^8" + }, + "peerDependenciesMeta": { + "@fastify/websocket": { + "optional": true + }, + "crossws": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, "node_modules/handlebars": { "version": "4.7.9", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", @@ -10153,6 +10345,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "bin": { "image-size": "bin/image-size.js" }, @@ -10740,6 +10933,13 @@ "node": ">=8" } }, + "node_modules/iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", + "dev": true, + "license": "MIT" + }, "node_modules/iterare": { "version": "1.2.1", "dev": true, @@ -12067,6 +12267,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -12080,6 +12281,7 @@ "dev": true, "license": "ISC", "optional": true, + "peer": true, "bin": { "semver": "bin/semver" } @@ -12089,6 +12291,7 @@ "dev": true, "license": "BSD-3-Clause", "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12279,6 +12482,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "dev": true, @@ -12706,6 +12916,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "bin": { "mime": "cli.js" }, @@ -13079,6 +13290,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.3", "sax": "^1.2.4" @@ -13938,6 +14150,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -14197,7 +14410,8 @@ "version": "1.0.1", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/psl": { "version": "1.9.0", @@ -14796,7 +15010,8 @@ "version": "1.4.1", "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/saxes": { "version": "6.0.0", @@ -15703,6 +15918,53 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/subscriptions-transport-ws": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", + "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", + "deprecated": "The `subscriptions-transport-ws` package is no longer maintained. We recommend you use `graphql-ws` instead. For help migrating Apollo software to `graphql-ws`, see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws For general help using `graphql-ws`, see https://github.com/enisdenjo/graphql-ws/blob/master/README.md", + "dev": true, + "license": "MIT", + "dependencies": { + "backo2": "^1.0.2", + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependencies": { + "graphql": "^15.7.2 || ^16.0.0" + } + }, + "node_modules/subscriptions-transport-ws/node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/subscriptions-transport-ws/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/superagent": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", @@ -15774,6 +16036,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "dev": true, @@ -16765,6 +17037,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -16782,6 +17055,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -16799,6 +17073,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -16816,6 +17091,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -16833,6 +17109,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -16850,6 +17127,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -16867,6 +17145,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -16884,6 +17163,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -16901,6 +17181,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16918,6 +17199,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16935,6 +17217,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16952,6 +17235,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16969,6 +17253,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16986,6 +17271,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -17003,6 +17289,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -17020,6 +17307,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -17037,6 +17325,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -17054,6 +17343,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -17071,6 +17361,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -17088,6 +17379,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -17105,6 +17397,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -17122,6 +17415,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -17139,6 +17433,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -17156,6 +17451,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -17173,6 +17469,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -17190,6 +17487,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -20226,6 +20524,7 @@ "devDependencies": { "@nestjs/common": "^11.1.19", "@nestjs/core": "^11.1.19", + "@nestjs/graphql": "^13.4.0", "@nestjs/platform-express": "^11.1.19", "@nestjs/testing": "^11.1.19", "@openfeature/core": "*", @@ -20236,8 +20535,14 @@ "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/graphql": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", "@openfeature/server-sdk": "^1.21.0", "rxjs": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@nestjs/graphql": { + "optional": true + } } }, "packages/nest/node_modules/@openfeature/server-sdk": { diff --git a/packages/nest/package.json b/packages/nest/package.json index 79506ae4a9..67061cfe2b 100644 --- a/packages/nest/package.json +++ b/packages/nest/package.json @@ -48,12 +48,19 @@ "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", - "rxjs": "^7.0.0 || ^8.0.0", - "@openfeature/server-sdk": "^1.21.0" + "@nestjs/graphql": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "@openfeature/server-sdk": "^1.21.0", + "rxjs": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@nestjs/graphql": { + "optional": true + } }, "devDependencies": { "@nestjs/common": "^11.1.19", "@nestjs/core": "^11.1.19", + "@nestjs/graphql": "^13.4.0", "@nestjs/platform-express": "^11.1.19", "@nestjs/testing": "^11.1.19", "@openfeature/core": "*", diff --git a/packages/nest/src/require-flags-enabled.decorator.ts b/packages/nest/src/require-flags-enabled.decorator.ts index dd651b9255..388f5c18bf 100644 --- a/packages/nest/src/require-flags-enabled.decorator.ts +++ b/packages/nest/src/require-flags-enabled.decorator.ts @@ -1,6 +1,6 @@ import type { CallHandler, ExecutionContext, HttpException, NestInterceptor } from '@nestjs/common'; import { applyDecorators, mixin, NotFoundException, UseInterceptors } from '@nestjs/common'; -import { getClientForEvaluation } from './utils'; +import { getClientForEvaluation, getRequestFromContext } from './utils'; import type { BooleanFlagKey, EvaluationContext } from '@openfeature/server-sdk'; import type { ContextFactory } from './context-factory'; @@ -84,7 +84,7 @@ const FlagsEnabledInterceptor = (props: RequireFlagsEnabledProps) => { constructor() {} async intercept(context: ExecutionContext, next: CallHandler) { - const req = context.switchToHttp().getRequest(); + const req = getRequestFromContext(context); const evaluationContext = props.contextFactory ? await props.contextFactory(context) : props.context; const client = getClientForEvaluation(props.domain, evaluationContext); diff --git a/packages/nest/src/utils.ts b/packages/nest/src/utils.ts index b517ad80fd..a96b2eeed7 100644 --- a/packages/nest/src/utils.ts +++ b/packages/nest/src/utils.ts @@ -1,6 +1,16 @@ import { withFrameworkMetadata } from '@openfeature/core'; import type { Client, EvaluationContext } from '@openfeature/server-sdk'; import { OpenFeature } from '@openfeature/server-sdk'; +import { ExecutionContext } from '@nestjs/common'; +import type { GqlExecutionContext as GqlExecutionContextClass, GqlContextType } from '@nestjs/graphql'; + +let GqlExecutionContext: typeof GqlExecutionContextClass | undefined; +try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + ({ GqlExecutionContext } = require('@nestjs/graphql') as typeof import('@nestjs/graphql')); +} catch { + // @nestjs/graphql is an optional peer dependency +} /** * Returns a domain scoped or the default OpenFeature client with the given context. @@ -14,3 +24,15 @@ export function getClientForEvaluation(domain?: string, context?: EvaluationCont 'nest', ); } + +/** + * @param {ExecutionContext} context NestJS's execution context + * @returns request object + */ +export const getRequestFromContext = (context: ExecutionContext): T | undefined => { + if (context.getType() === 'http') { + return context.switchToHttp().getRequest(); + } else if (context.getType() === 'graphql') { + return GqlExecutionContext?.create(context)?.getContext()?.req as T; + } +}; From 903a553e55c5b8b8dcbfb8dd30abeff866438d99 Mon Sep 17 00:00:00 2001 From: Benjamin Kenawell Date: Thu, 14 May 2026 15:46:47 -0400 Subject: [PATCH 2/2] add tests for getRequestFromContext Signed-off-by: Benjamin Kenawell --- packages/nest/src/utils.ts | 8 +++-- packages/nest/test/utils.spec.ts | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 packages/nest/test/utils.spec.ts diff --git a/packages/nest/src/utils.ts b/packages/nest/src/utils.ts index a96b2eeed7..592762c3a4 100644 --- a/packages/nest/src/utils.ts +++ b/packages/nest/src/utils.ts @@ -1,12 +1,12 @@ import { withFrameworkMetadata } from '@openfeature/core'; import type { Client, EvaluationContext } from '@openfeature/server-sdk'; import { OpenFeature } from '@openfeature/server-sdk'; -import { ExecutionContext } from '@nestjs/common'; +import type { ExecutionContext } from '@nestjs/common'; import type { GqlExecutionContext as GqlExecutionContextClass, GqlContextType } from '@nestjs/graphql'; let GqlExecutionContext: typeof GqlExecutionContextClass | undefined; try { - // eslint-disable-next-line @typescript-eslint/no-require-imports + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/consistent-type-imports ({ GqlExecutionContext } = require('@nestjs/graphql') as typeof import('@nestjs/graphql')); } catch { // @nestjs/graphql is an optional peer dependency @@ -26,9 +26,11 @@ export function getClientForEvaluation(domain?: string, context?: EvaluationCont } /** + * @template T type of the returned request. Likely a Express or Fastify request * @param {ExecutionContext} context NestJS's execution context - * @returns request object + * @returns {T | undefined} request object */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const getRequestFromContext = (context: ExecutionContext): T | undefined => { if (context.getType() === 'http') { return context.switchToHttp().getRequest(); diff --git a/packages/nest/test/utils.spec.ts b/packages/nest/test/utils.spec.ts new file mode 100644 index 0000000000..7bef701ae5 --- /dev/null +++ b/packages/nest/test/utils.spec.ts @@ -0,0 +1,57 @@ +import type { ExecutionContext } from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; +import { getRequestFromContext } from '../src/utils'; + +jest.mock('@nestjs/graphql', () => ({ + GqlExecutionContext: { + create: jest.fn(), + }, +})); + +const mockRequest = { headers: {}, body: {} }; + +describe('getRequestFromContext', () => { + describe('when context type is http', () => { + it('should return the HTTP request', () => { + const context = { + getType: () => 'http', + switchToHttp: () => ({ getRequest: () => mockRequest }), + } as unknown as ExecutionContext; + + expect(getRequestFromContext(context)).toBe(mockRequest); + }); + }); + + describe('when context type is graphql', () => { + describe('and @nestjs/graphql is available', () => { + it('should return the request from the GraphQL context', () => { + const context = { getType: () => 'graphql' } as unknown as ExecutionContext; + (GqlExecutionContext.create as jest.Mock).mockReturnValue({ + getContext: () => ({ req: mockRequest }), + }); + + expect(getRequestFromContext(context)).toBe(mockRequest); + }); + }); + + it('should return undefined when @nestjs/graphql is not available', () => { + jest.resetModules(); + jest.doMock('@nestjs/graphql', () => { + throw new Error('Cannot find module'); + }); + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/consistent-type-imports + const { getRequestFromContext: fn } = require('../src/utils') as typeof import('../src/utils'); + const context = { getType: () => 'graphql' } as unknown as ExecutionContext; + expect(fn(context)).toBeUndefined(); + jest.resetModules(); + }); + }); + + describe('when context type is unknown', () => { + it('should return undefined', () => { + const context = { getType: () => 'rpc' } as unknown as ExecutionContext; + + expect(getRequestFromContext(context)).toBeUndefined(); + }); + }); +});