From 1946228b077dce9032849716bfef057858074f2e Mon Sep 17 00:00:00 2001 From: Jan Hug Date: Thu, 9 Apr 2026 09:06:38 +0200 Subject: [PATCH 1/2] use client/server options from layers (#81) --- docs/configuration/client-options.md | 8 +++ docs/configuration/server-options.md | 8 +++ .../app/graphqlMiddleware.clientOptions.ts | 9 ++++ .../server/graphqlMiddleware.serverOptions.ts | 11 +++- .../server/graphqlMiddleware.serverOptions.ts | 12 ----- src/build/ModuleHelper.ts | 36 ++++++++----- test/layer-options.test.ts | 50 +++++++++++++++++++ .../serverHandler/doGraphqlRequest.test.ts | 22 +++----- 8 files changed, 114 insertions(+), 42 deletions(-) create mode 100644 playground-layers/layers/test-layer/app/graphqlMiddleware.clientOptions.ts delete mode 100644 playground-layers/server/graphqlMiddleware.serverOptions.ts create mode 100644 test/layer-options.test.ts diff --git a/docs/configuration/client-options.md b/docs/configuration/client-options.md index 45da1a2..927b9aa 100644 --- a/docs/configuration/client-options.md +++ b/docs/configuration/client-options.md @@ -34,6 +34,14 @@ export default defineGraphqlClientOptions({}) ::: +### Nuxt Layers + +The client options file is also discovered from +[Nuxt layers](https://nuxt.com/docs/getting-started/layers). The module checks +all layers in order, starting with the main app. The first layer that contains a +`graphqlMiddleware.clientOptions` file wins — options from multiple layers are +**not** merged. + ## Defining Client Context Implement the `buildClientContext()` method to return an object with string diff --git a/docs/configuration/server-options.md b/docs/configuration/server-options.md index 88d8dbb..53659c4 100644 --- a/docs/configuration/server-options.md +++ b/docs/configuration/server-options.md @@ -21,6 +21,14 @@ export default defineGraphqlServerOptions({ ::: +### Nuxt Layers + +The server options file is also discovered from +[Nuxt layers](https://nuxt.com/docs/getting-started/layers). The module checks +all layers in order, starting with the main app. The first layer that contains a +`graphqlMiddleware.serverOptions` file wins — options from multiple layers are +**not** merged. + ## doGraphqlRequest Provide a custom method that performs the request to the GraphQL server. It diff --git a/playground-layers/layers/test-layer/app/graphqlMiddleware.clientOptions.ts b/playground-layers/layers/test-layer/app/graphqlMiddleware.clientOptions.ts new file mode 100644 index 0000000..783992d --- /dev/null +++ b/playground-layers/layers/test-layer/app/graphqlMiddleware.clientOptions.ts @@ -0,0 +1,9 @@ +import { defineGraphqlClientOptions } from './../../../../src/client-options' + +export default defineGraphqlClientOptions({ + buildClientContext() { + return { + fromLayer: 'true', + } + }, +}) diff --git a/playground-layers/layers/test-layer/server/graphqlMiddleware.serverOptions.ts b/playground-layers/layers/test-layer/server/graphqlMiddleware.serverOptions.ts index 357dda5..8a4075c 100644 --- a/playground-layers/layers/test-layer/server/graphqlMiddleware.serverOptions.ts +++ b/playground-layers/layers/test-layer/server/graphqlMiddleware.serverOptions.ts @@ -1,3 +1,12 @@ import { defineGraphqlServerOptions } from './../../../../src/server-options' -export default defineGraphqlServerOptions({}) +export default defineGraphqlServerOptions({ + serverFetchOptions: function (_event, _operation, operationName) { + const headers: Record = { + 'x-nuxt-header-server': 'Value from layer', + authentication: 'server-token', + 'x-apollo-operation-name': operationName, + } + return { headers } + }, +}) diff --git a/playground-layers/server/graphqlMiddleware.serverOptions.ts b/playground-layers/server/graphqlMiddleware.serverOptions.ts deleted file mode 100644 index 811b868..0000000 --- a/playground-layers/server/graphqlMiddleware.serverOptions.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineGraphqlServerOptions } from './../../src/server-options' - -export default defineGraphqlServerOptions({ - serverFetchOptions: function (event, _operation, operationName) { - const headers: Record = { - 'x-nuxt-header-server': 'Value from server', - authentication: 'server-token', - 'x-apollo-operation-name': operationName, - } - return { headers } - }, -}) diff --git a/src/build/ModuleHelper.ts b/src/build/ModuleHelper.ts index 55e82b8..984fb84 100644 --- a/src/build/ModuleHelper.ts +++ b/src/build/ModuleHelper.ts @@ -228,17 +228,32 @@ export class ModuleHelper { } /** - * Find the path to the graphqlMiddleware.serverOptions.ts file. + * Find a file across all Nuxt layers. The first layer (main app) takes + * precedence over extended layers. */ + private findInLayers( + fileName: string, + getDir: (layer: (typeof this.nuxt.options._layers)[number]) => string, + ): string | null { + for (const layer of this.nuxt.options._layers) { + const dir = getDir(layer) + const resolver = createResolver(dir) + const found = fileExists(resolver.resolve(fileName)) + if (found) { + return found + } + } + return null + } + private findServerOptions(): string | null { - // Look for the file in the server directory. - const newPath = this.resolvers.server.resolve( + const found = this.findInLayers( 'graphqlMiddleware.serverOptions', + (layer) => layer.config.serverDir ?? layer.config.rootDir + '/server', ) - const serverPath = fileExists(newPath) - if (serverPath) { - return serverPath + if (found) { + return found } // Check for previous locations of the server options file that are not @@ -268,15 +283,10 @@ export class ModuleHelper { } private findClientOptions(): string | null { - const clientOptionsPath = this.resolvers.app.resolve( + return this.findInLayers( 'graphqlMiddleware.clientOptions', + (layer) => layer.config.appDir ?? layer.config.srcDir, ) - - if (fileExists(clientOptionsPath)) { - return clientOptionsPath - } - - return null } /** diff --git a/test/layer-options.test.ts b/test/layer-options.test.ts new file mode 100644 index 0000000..5933f13 --- /dev/null +++ b/test/layer-options.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from 'vitest' +import { readFileSync } from 'fs' +import { join } from 'path' + +const PLAYGROUND_LAYERS_DIR = join( + process.cwd(), + 'playground-layers/.nuxt/nuxt-graphql-middleware', +) + +const PLAYGROUND_DIR = join( + process.cwd(), + 'playground/.nuxt/nuxt-graphql-middleware', +) + +function readGenerated(dir: string, file: string): string { + return readFileSync(join(dir, file), 'utf-8') +} + +/** + * @see https://github.com/dulnan/nuxt-graphql-middleware/issues/81 + */ +describe('Layer options discovery', () => { + test('playground-layers: server options are found from the layer', () => { + const content = readGenerated(PLAYGROUND_LAYERS_DIR, 'server-options.js') + expect(content).toContain( + "import serverOptions from '../../layers/test-layer/server/graphqlMiddleware.serverOptions.ts'", + ) + }) + + test('playground-layers: client options are found from the layer', () => { + const content = readGenerated(PLAYGROUND_LAYERS_DIR, 'client-options.js') + expect(content).toContain( + "import clientOptions from '../../layers/test-layer/app/graphqlMiddleware.clientOptions.ts'", + ) + }) + + test('playground: server options are found from the main app (no layers involved)', () => { + const content = readGenerated(PLAYGROUND_DIR, 'server-options.js') + expect(content).toContain( + "import serverOptions from '../../server/graphqlMiddleware.serverOptions.ts'", + ) + }) + + test('playground: client options are found from the main app', () => { + const content = readGenerated(PLAYGROUND_DIR, 'client-options.js') + expect(content).toContain( + "import clientOptions from '../../app/graphqlMiddleware.clientOptions", + ) + }) +}) diff --git a/test/runtime/serverHandler/doGraphqlRequest.test.ts b/test/runtime/serverHandler/doGraphqlRequest.test.ts index e2a35c2..1c17164 100644 --- a/test/runtime/serverHandler/doGraphqlRequest.test.ts +++ b/test/runtime/serverHandler/doGraphqlRequest.test.ts @@ -59,19 +59,11 @@ const graphqlEndpointCalls: Array<{ vi.mock('#nuxt-graphql-middleware/server-options', () => { return { serverOptions: { - serverFetchOptions: ( - _event: any, - operation: any, - operationName: any, - ) => { + serverFetchOptions: (_event: any, operation: any, operationName: any) => { serverFetchOptionsCalls.push({ operation, operationName }) return {} }, - graphqlEndpoint: ( - _event: any, - operation: any, - operationName: any, - ) => { + graphqlEndpoint: (_event: any, operation: any, operationName: any) => { graphqlEndpointCalls.push({ operation, operationName }) return 'http://localhost/graphql' }, @@ -99,9 +91,8 @@ describe('doGraphqlRequest', () => { serverFetchOptionsCalls.length = 0 graphqlEndpointCalls.length = 0 - const { doGraphqlRequest } = await import( - './../../../src/runtime/server/utils/doGraphqlRequest' - ) + const { doGraphqlRequest } = + await import('./../../../src/runtime/server/utils/doGraphqlRequest') await doGraphqlRequest( { @@ -129,9 +120,8 @@ describe('doGraphqlRequest', () => { serverFetchOptionsCalls.length = 0 graphqlEndpointCalls.length = 0 - const { doGraphqlRequest } = await import( - './../../../src/runtime/server/utils/doGraphqlRequest' - ) + const { doGraphqlRequest } = + await import('./../../../src/runtime/server/utils/doGraphqlRequest') await doGraphqlRequest( { From 31f78789f3e9d46bb23536e31ed1f08652c75ecc Mon Sep 17 00:00:00 2001 From: Jan Hug Date: Thu, 9 Apr 2026 09:28:25 +0200 Subject: [PATCH 2/2] fix test --- test/e2e/mcp/module-config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/mcp/module-config.test.ts b/test/e2e/mcp/module-config.test.ts index 5368255..283c6ba 100644 --- a/test/e2e/mcp/module-config.test.ts +++ b/test/e2e/mcp/module-config.test.ts @@ -131,7 +131,7 @@ describe('MCP Module Config Tool', async () => { expect(structured.paths).toMatchInlineSnapshot(` { - "clientOptions": "./app/graphqlMiddleware.clientOptions", + "clientOptions": "./app/graphqlMiddleware.clientOptions.ts", "documentTypes": ".nuxt/graphql-operations/index.d.ts", "runtimeTypes": "../../../src/runtime/types.ts", "schema": "../schema.graphql",