diff --git a/.changeset/@graphql-yoga_plugin-sofa-3559-dependencies.md b/.changeset/@graphql-yoga_plugin-sofa-3559-dependencies.md new file mode 100644 index 0000000000..5e467beef8 --- /dev/null +++ b/.changeset/@graphql-yoga_plugin-sofa-3559-dependencies.md @@ -0,0 +1,5 @@ +--- +"@graphql-yoga/plugin-sofa": patch +--- +dependencies updates: + - Updated dependency [`sofa-api@^0.18.8` ↗︎](https://www.npmjs.com/package/sofa-api/v/0.18.8) (from `^0.18.7`, in `dependencies`) diff --git a/.changeset/soft-turtles-deny.md b/.changeset/soft-turtles-deny.md new file mode 100644 index 0000000000..b6c1869436 --- /dev/null +++ b/.changeset/soft-turtles-deny.md @@ -0,0 +1,5 @@ +--- +'@graphql-yoga/plugin-sofa': patch +--- + +Fix the issue when SOFA returns 404 response from error extensions returned by a resolver, it will cause the server to continue the request handling with Yoga but instead it should return the response with 404 and the body SOFA returns. \ No newline at end of file diff --git a/packages/plugins/sofa/package.json b/packages/plugins/sofa/package.json index 3a8634bc9a..61e8a0d317 100644 --- a/packages/plugins/sofa/package.json +++ b/packages/plugins/sofa/package.json @@ -48,7 +48,7 @@ }, "dependencies": { "@graphql-tools/utils": "^10.3.2", - "sofa-api": "^0.18.7" + "sofa-api": "^0.18.8" }, "devDependencies": { "graphql-yoga": "workspace:*" diff --git a/packages/plugins/sofa/src/index.ts b/packages/plugins/sofa/src/index.ts index a84fece75e..d64a8c98ec 100644 --- a/packages/plugins/sofa/src/index.ts +++ b/packages/plugins/sofa/src/index.ts @@ -1,7 +1,6 @@ import { ExecutionArgs, ExecutionResult, SubscriptionArgs } from 'graphql'; -import { Plugin, YogaInitialContext, YogaServerInstance } from 'graphql-yoga'; +import { mapMaybePromise, Plugin, YogaInitialContext, YogaServerInstance } from 'graphql-yoga'; import { useSofa as createSofaHandler } from 'sofa-api'; -import { isPromise } from '@graphql-tools/utils'; import { SofaHandler } from './types.js'; type SofaHandlerConfig = Parameters[0]; @@ -33,15 +32,10 @@ export function useSofa(config: SofaPluginConfig): Plugin { schema: onSchemaChangeEventPayload.schema, context(serverContext: YogaInitialContext) { const enveloped = getEnveloped(serverContext); - const contextValue$ = enveloped.contextFactory(serverContext); - if (isPromise(contextValue$)) { - return contextValue$.then(contextValue => { - envelopedByContext.set(contextValue, enveloped); - return contextValue; - }); - } - envelopedByContext.set(contextValue$, enveloped); - return contextValue$; + return mapMaybePromise(enveloped.contextFactory(serverContext), contextValue$ => { + envelopedByContext.set(contextValue$, enveloped); + return contextValue$; + }); }, execute( ...args: @@ -115,10 +109,12 @@ export function useSofa(config: SofaPluginConfig): Plugin { }, }); }, - async onRequest({ request, serverContext, endResponse }) { - const response = await sofaHandler(request, serverContext as Record); - if (response != null && response.status !== 404) { - endResponse(response); + async onRequest({ request, endResponse, serverContext, url }) { + if (url.pathname.startsWith(config.basePath)) { + const res = await sofaHandler.handleRequest(request, serverContext); + if (res) { + endResponse(res); + } } }, }; diff --git a/packages/plugins/sofa/tests/sofa.test.ts b/packages/plugins/sofa/tests/sofa.test.ts index 15a84067b8..fc4b83b067 100644 --- a/packages/plugins/sofa/tests/sofa.test.ts +++ b/packages/plugins/sofa/tests/sofa.test.ts @@ -1,4 +1,4 @@ -import { createSchema, createYoga } from 'graphql-yoga'; +import { createGraphQLError, createSchema, createYoga } from 'graphql-yoga'; import { useSofa } from '@graphql-yoga/plugin-sofa'; describe('SOFA Plugin', () => { @@ -31,4 +31,52 @@ describe('SOFA Plugin', () => { }); await expect(res.text()).resolves.toEqual('"Hello, test"'); }); + it('forwards error extensions correctly', async () => { + const yoga = createYoga({ + schema: createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + me: Account + } + type Account { + id: ID! + name: String! + } + `, + resolvers: { + Query: { + me: () => { + throw createGraphQLError('account not found', { + extensions: { + code: 'ACCOUNT_NOT_FOUND', + http: { status: 404 }, + }, + }); + }, + }, + }, + }), + plugins: [ + useSofa({ + basePath: '/api', + }), + ], + }); + for (let i = 0; i < 10; i++) { + const res = await yoga.fetch('http://localhost/api/me'); + expect(res.status).toBe(404); + const json = await res.json(); + expect(json).toEqual({ + errors: [ + { + message: 'account not found', + extensions: { + code: 'ACCOUNT_NOT_FOUND', + }, + path: ['me'], + }, + ], + }); + } + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba61ae2b51..b54965cd33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2046,8 +2046,8 @@ importers: specifier: 16.10.0 version: 16.10.0 sofa-api: - specifier: ^0.18.7 - version: 0.18.7(graphql@16.10.0) + specifier: ^0.18.8 + version: 0.18.8(graphql@16.10.0) devDependencies: graphql-yoga: specifier: workspace:* @@ -2353,7 +2353,6 @@ packages: deprecated: |- AWS CDK v1 has reached End-of-Support on 2023-06-01. This package is no longer being updated, and users should migrate to AWS CDK v2. - For more information on how to migrate, see https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html peerDependencies: '@aws-cdk/aws-certificatemanager': 1.204.0 @@ -2630,7 +2629,6 @@ packages: deprecated: |- AWS CDK v1 has reached End-of-Support on 2023-06-01. This package is no longer being updated, and users should migrate to AWS CDK v2. - For more information on how to migrate, see https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html peerDependencies: '@aws-cdk/aws-applicationautoscaling': 1.204.0 @@ -2817,7 +2815,6 @@ packages: deprecated: |- AWS CDK v1 has reached End-of-Support on 2023-06-01. This package is no longer being updated, and users should migrate to AWS CDK v2. - For more information on how to migrate, see https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html peerDependencies: '@aws-cdk/cloud-assembly-schema': 1.204.0 @@ -15151,8 +15148,8 @@ packages: resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sofa-api@0.18.7: - resolution: {integrity: sha512-0boXHTAgSuNTLPsfAEmlgW7bvew8b/4YRM8iZwY7NiJgC4HVYPYHu5NtrLFiY618rdgHqk7Up7PEekgEAxJaTw==} + sofa-api@0.18.8: + resolution: {integrity: sha512-wTDvSyFB4LjnOTRRi86rCZ5TtYAL0fbFlMVlUnOXGBfcc4t+4BFuBxMEMkbkw0aouIMFP+2E1QemRpPqr/rW6Q==} peerDependencies: graphql: 16.10.0 @@ -20059,7 +20056,7 @@ snapshots: graphql: 16.10.0 graphql-request: 6.1.0(encoding@0.1.13)(graphql@16.10.0) http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@9.4.0) jose: 5.9.6 js-yaml: 4.1.0 lodash: 4.17.21 @@ -20085,7 +20082,7 @@ snapshots: '@graphql-tools/schema@10.0.10(graphql@16.10.0)': dependencies: - '@graphql-tools/merge': 9.0.11(graphql@16.10.0) + '@graphql-tools/merge': 9.0.14(graphql@16.10.0) '@graphql-tools/utils': 10.6.4(graphql@16.10.0) graphql: 16.10.0 tslib: 2.8.1 @@ -20784,7 +20781,7 @@ snapshots: dependencies: consola: 3.2.3 detect-libc: 2.0.3 - https-proxy-agent: 7.0.5(supports-color@9.4.0) + https-proxy-agent: 7.0.6(supports-color@9.4.0) node-fetch: 2.7.0(encoding@0.1.13) nopt: 8.0.0 semver: 7.6.3 @@ -21423,7 +21420,7 @@ snapshots: unixify: 1.0.0 urlpattern-polyfill: 8.0.2 yargs: 17.7.2 - zod: 3.23.8 + zod: 3.24.1 transitivePeerDependencies: - encoding - rollup @@ -21531,7 +21528,7 @@ snapshots: dependencies: agent-base: 7.1.3 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@9.4.0) lru-cache: 10.4.3 socks-proxy-agent: 8.0.5 transitivePeerDependencies: @@ -25826,11 +25823,9 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.7(supports-color@9.4.0): + debug@4.3.7: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 9.4.0 debug@4.4.0(supports-color@9.4.0): dependencies: @@ -26122,7 +26117,7 @@ snapshots: base64id: 2.0.0 cookie: 0.7.2 cors: 2.8.5 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 engine.io-parser: 5.2.3 ws: 8.17.1 transitivePeerDependencies: @@ -26457,7 +26452,7 @@ snapshots: eslint: 9.17.0(jiti@2.4.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0(jiti@2.4.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.17.0(jiti@2.4.1)) eslint-plugin-react: 7.37.2(eslint@9.17.0(jiti@2.4.1)) eslint-plugin-react-hooks: 5.1.0(eslint@9.17.0(jiti@2.4.1)) @@ -26492,7 +26487,7 @@ snapshots: is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0(jiti@2.4.1)) transitivePeerDependencies: - supports-color @@ -26596,7 +26591,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0(jiti@2.4.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -27114,7 +27109,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.7(supports-color@9.4.0) + debug: 4.4.0(supports-color@9.4.0) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -27444,7 +27439,7 @@ snapshots: follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 follow-redirects@1.15.9(debug@4.4.0): optionalDependencies: @@ -28267,14 +28262,14 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.5(supports-color@9.4.0): + https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.3 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.4.0(supports-color@9.4.0) transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@9.4.0): dependencies: agent-base: 7.1.3 debug: 4.4.0(supports-color@9.4.0) @@ -29423,7 +29418,7 @@ snapshots: lambda-local@2.2.0: dependencies: commander: 10.0.1 - dotenv: 16.4.5 + dotenv: 16.4.7 winston: 3.17.0 langium@3.0.0: @@ -30726,7 +30721,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cron-parser: 4.9.0 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 decache: 4.6.2 dot-prop: 9.0.0 dotenv: 16.4.5 @@ -30752,7 +30747,7 @@ snapshots: hasha: 5.2.2 http-proxy: 1.18.1(debug@4.3.7) http-proxy-middleware: 2.0.7(@types/express@4.17.21)(debug@4.3.7) - https-proxy-agent: 7.0.5(supports-color@9.4.0) + https-proxy-agent: 7.0.5 inquirer: 6.5.2 inquirer-autocomplete-prompt: 1.4.0(inquirer@6.5.2) ipx: 2.1.0(@netlify/blobs@8.1.0)(ioredis@5.4.1) @@ -32982,7 +32977,7 @@ snapshots: socket.io-adapter@2.5.5: dependencies: - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 ws: 8.17.1 transitivePeerDependencies: - bufferutil @@ -32992,7 +32987,7 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -33001,7 +32996,7 @@ snapshots: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.3.7 engine.io: 6.6.2 socket.io-adapter: 2.5.5 socket.io-parser: 4.2.4 @@ -33031,10 +33026,10 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 - sofa-api@0.18.7(graphql@16.10.0): + sofa-api@0.18.8(graphql@16.10.0): dependencies: '@graphql-tools/utils': 10.6.4(graphql@16.10.0) - '@whatwg-node/fetch': 0.9.23 + '@whatwg-node/fetch': 0.10.1 ansi-colors: 4.1.3 fets: 0.8.4 graphql: 16.10.0 @@ -33504,7 +33499,7 @@ snapshots: tabtab@3.0.2: dependencies: - debug: 4.3.7(supports-color@9.4.0) + debug: 4.4.0(supports-color@9.4.0) es6-promisify: 6.1.1 inquirer: 6.5.2 minimist: 1.2.8 @@ -34469,7 +34464,7 @@ snapshots: dependencies: chalk: 4.1.2 commander: 9.5.0 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.4.0(supports-color@9.4.0) transitivePeerDependencies: - supports-color diff --git a/website/src/pages/docs/features/sofa-api.mdx b/website/src/pages/docs/features/sofa-api.mdx index a7f2c5e9f1..7d5bac0b21 100644 --- a/website/src/pages/docs/features/sofa-api.mdx +++ b/website/src/pages/docs/features/sofa-api.mdx @@ -4,6 +4,8 @@ description: all of that into REST API. --- +import { Callout } from '@theguild/components' + # Sofa API Sofa takes your GraphQL Schema, looks for available queries, mutations and subscriptions and turns @@ -155,3 +157,8 @@ server.listen(4000, () => { ``` You can start the server and visit http://localhost:4000/swagger to see the Swagger UI. + + + Check out the [SOFA documentation](https://sofa-api.com) for more information about SOFA, and the + all features it provides. +