nitro-graphql 1.7.0-beta.5 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,8 +1,15 @@
1
1
  import { StandardSchemaV1 } from "./types/standard-schema.mjs";
2
- import { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, SubscriptionsConfig, TypesConfig } from "./types/index.mjs";
2
+ import { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, SecurityConfig, SubscriptionsConfig, TypesConfig } from "./types/index.mjs";
3
3
  import * as nitropack0 from "nitropack";
4
4
 
5
5
  //#region src/index.d.ts
6
+
7
+ /**
8
+ * Resolve security config with environment-aware defaults
9
+ * In production: introspection off, playground off, errors masked, suggestions disabled
10
+ * In development: introspection on, playground on, errors shown, suggestions enabled
11
+ */
12
+ declare function resolveSecurityConfig(config?: SecurityConfig): Required<SecurityConfig>;
6
13
  declare const _default: nitropack0.NitroModule;
7
14
  //#endregion
8
- export { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, StandardSchemaV1, SubscriptionsConfig, TypesConfig, _default as default };
15
+ export { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, SecurityConfig, StandardSchemaV1, SubscriptionsConfig, TypesConfig, _default as default, resolveSecurityConfig };
package/dist/index.mjs CHANGED
@@ -14,6 +14,20 @@ import { defineNitroModule } from "nitropack/kit";
14
14
  import { dirname, join, relative, resolve } from "pathe";
15
15
 
16
16
  //#region src/index.ts
17
+ /**
18
+ * Resolve security config with environment-aware defaults
19
+ * In production: introspection off, playground off, errors masked, suggestions disabled
20
+ * In development: introspection on, playground on, errors shown, suggestions enabled
21
+ */
22
+ function resolveSecurityConfig(config) {
23
+ const isProd = process.env.NODE_ENV === "production";
24
+ return {
25
+ introspection: config?.introspection ?? !isProd,
26
+ playground: config?.playground ?? !isProd,
27
+ maskErrors: config?.maskErrors ?? isProd,
28
+ disableSuggestions: config?.disableSuggestions ?? isProd
29
+ };
30
+ }
17
31
  var src_default = defineNitroModule({
18
32
  name: "nitro-graphql",
19
33
  async setup(nitro) {
@@ -50,13 +64,15 @@ var src_default = defineNitroModule({
50
64
  };
51
65
  }
52
66
  });
67
+ const securityConfig = resolveSecurityConfig(nitro.options.graphql?.security);
53
68
  nitro.options.runtimeConfig.graphql = defu(nitro.options.runtimeConfig.graphql || {}, {
54
69
  endpoint: {
55
70
  graphql: "/api/graphql",
56
71
  healthCheck: "/api/graphql/health",
57
72
  ws: "/api/graphql/ws"
58
73
  },
59
- playground: true
74
+ playground: securityConfig.playground,
75
+ security: securityConfig
60
76
  });
61
77
  if (nitro.options.graphql?.federation?.enabled) consola.info(`Apollo Federation enabled for service: ${nitro.options.graphql.federation.serviceName || "unnamed"}`);
62
78
  const graphqlBuildDir = resolve(nitro.options.buildDir, "graphql");
@@ -118,42 +134,32 @@ var src_default = defineNitroModule({
118
134
  const docs = await scanDocs(nitro);
119
135
  nitro.scanDocuments = docs;
120
136
  if (nitro.options.dev) {
137
+ const runtimeSecurityConfig = nitro.options.runtimeConfig.graphql?.security;
138
+ const isProd = process.env.NODE_ENV === "production";
121
139
  consola.box({
122
140
  title: "Nitro GraphQL",
123
141
  message: [
124
142
  `Framework: ${nitro.options.graphql?.framework || "Not configured"}`,
143
+ `Environment: ${isProd ? "production" : "development"}`,
125
144
  `Schemas: ${schemas.length}`,
126
145
  `Resolvers: ${resolvers.length}`,
127
146
  `Directives: ${directives$1.length}`,
128
147
  `Documents: ${docs.length}`,
129
148
  "",
149
+ "Security:",
150
+ `├─ Introspection: ${runtimeSecurityConfig?.introspection ? "enabled" : "disabled"}`,
151
+ `├─ Playground: ${runtimeSecurityConfig?.playground ? "enabled" : "disabled"}`,
152
+ `├─ Error Masking: ${runtimeSecurityConfig?.maskErrors ? "enabled" : "disabled"}`,
153
+ `└─ Field Suggestions: ${runtimeSecurityConfig?.disableSuggestions ? "disabled" : "enabled"}`,
154
+ "",
130
155
  "Debug Dashboard: /_nitro/graphql/debug"
131
156
  ].join("\n"),
132
157
  style: {
133
- borderColor: "cyan",
158
+ borderColor: isProd ? "yellow" : "cyan",
134
159
  borderStyle: "rounded"
135
160
  }
136
161
  });
137
- if (resolvers.length > 0) {
138
- const totalExports = resolvers.reduce((sum, r) => sum + r.imports.length, 0);
139
- const typeCount = {
140
- query: 0,
141
- mutation: 0,
142
- resolver: 0,
143
- type: 0,
144
- subscription: 0,
145
- directive: 0
146
- };
147
- for (const resolver of resolvers) for (const imp of resolver.imports) if (imp.type in typeCount) typeCount[imp.type]++;
148
- const breakdown = [];
149
- if (typeCount.query > 0) breakdown.push(`${typeCount.query} query`);
150
- if (typeCount.mutation > 0) breakdown.push(`${typeCount.mutation} mutation`);
151
- if (typeCount.resolver > 0) breakdown.push(`${typeCount.resolver} resolver`);
152
- if (typeCount.type > 0) breakdown.push(`${typeCount.type} type`);
153
- if (typeCount.subscription > 0) breakdown.push(`${typeCount.subscription} subscription`);
154
- if (typeCount.directive > 0) breakdown.push(`${typeCount.directive} directive`);
155
- if (breakdown.length > 0) consola.success(`[nitro-graphql] ${totalExports} resolver export(s): ${breakdown.join(", ")}`);
156
- } else consola.warn("[nitro-graphql] No resolvers found. Check /_nitro/graphql/debug for details.");
162
+ if (resolvers.length === 0) consola.warn("[nitro-graphql] No resolvers found. Check /_nitro/graphql/debug for details.");
157
163
  }
158
164
  });
159
165
  await rollupConfig(nitro);
@@ -196,16 +202,15 @@ var src_default = defineNitroModule({
196
202
  route: wsEndpoint,
197
203
  handler: join(runtime, "apollo-server-ws")
198
204
  });
205
+ nitro.options.plugins ??= [];
206
+ nitro.options.plugins.push(join(runtime, "ws-shutdown"));
199
207
  consola.info(`[nitro-graphql] WebSocket subscriptions enabled at: ${wsEndpoint}`);
200
208
  }
201
- if (nitro.options.dev) {
202
- nitro.options.handlers.push({
203
- route: "/_nitro/graphql/debug",
204
- handler: join(runtime, "debug"),
205
- method: "get"
206
- });
207
- consola.info("[nitro-graphql] Debug dashboard available at: /_nitro/graphql/debug");
208
- }
209
+ if (nitro.options.dev) nitro.options.handlers.push({
210
+ route: "/_nitro/graphql/debug",
211
+ handler: join(runtime, "debug"),
212
+ method: "get"
213
+ });
209
214
  if (nitro.options.imports) {
210
215
  nitro.options.imports.presets ??= [];
211
216
  nitro.options.imports.presets.push({
@@ -350,4 +355,4 @@ declare module 'h3' {
350
355
  });
351
356
 
352
357
  //#endregion
353
- export { src_default as default };
358
+ export { src_default as default, resolveSecurityConfig };
package/dist/rollup.mjs CHANGED
@@ -62,15 +62,13 @@ function virtualSchemas(app) {
62
62
  }
63
63
  const importStatements = imports.map((handler) => `import ${getImportId(handler)} from '${handler}';`);
64
64
  const schemaArray = imports.map((h) => `{ def: ${getImportId(h)} }`);
65
- const code = `
65
+ return `
66
66
  ${importStatements.join("\n")}
67
67
 
68
68
  export const schemas = [
69
69
  ${schemaArray.join(",\n")}
70
70
  ];
71
71
  `;
72
- if (app.options.dev) app.logger.success(`[nitro-graphql] Generated virtual schema module: ${imports.length} schema(s)`);
73
- return code;
74
72
  } catch (error) {
75
73
  app.logger.error("[nitro-graphql] Failed to generate virtual schema module:", error);
76
74
  return "export const schemas = []";
@@ -108,7 +106,7 @@ function virtualResolvers(app) {
108
106
  for (const msg of invalidImports) app.logger.warn(` - ${msg}`);
109
107
  }
110
108
  const data = imports.map(({ imports: importList }) => importList.map((i) => `{ resolver: ${i.as} }`).join(",\n")).filter(Boolean).join(",\n");
111
- const code = [
109
+ return [
112
110
  ...importsContent,
113
111
  "",
114
112
  "export const resolvers = [",
@@ -116,11 +114,6 @@ function virtualResolvers(app) {
116
114
  "]",
117
115
  ""
118
116
  ].join("\n");
119
- if (app.options.dev) {
120
- const totalExports = imports.reduce((sum, r) => sum + r.imports.length, 0);
121
- app.logger.success(`[nitro-graphql] Generated virtual resolver module: ${totalExports} export(s) from ${imports.length} file(s)`);
122
- }
123
- return code;
124
117
  } catch (error) {
125
118
  app.logger.error("[nitro-graphql] Failed to generate virtual resolver module:", error);
126
119
  return "export const resolvers = []";
@@ -155,7 +148,7 @@ function virtualDirectives(app) {
155
148
  for (const msg of invalidImports) app.logger.warn(` - ${msg}`);
156
149
  }
157
150
  const data = imports.map(({ imports: importList }) => importList.map((i) => `{ directive: ${i.as} }`).join(",\n")).filter(Boolean).join(",\n");
158
- const code = [
151
+ return [
159
152
  ...importsContent,
160
153
  "",
161
154
  "export const directives = [",
@@ -163,11 +156,6 @@ function virtualDirectives(app) {
163
156
  "]",
164
157
  ""
165
158
  ].join("\n");
166
- if (app.options.dev) {
167
- const totalExports = imports.reduce((sum, d) => sum + d.imports.length, 0);
168
- app.logger.success(`[nitro-graphql] Generated virtual directive module: ${totalExports} directive(s) from ${imports.length} file(s)`);
169
- }
170
- return code;
171
159
  } catch (error) {
172
160
  app.logger.error("[nitro-graphql] Failed to generate virtual directive module:", error);
173
161
  return "export const directives = []";
@@ -187,7 +175,10 @@ export { importedConfig }
187
175
  function virtualModuleConfig(app) {
188
176
  app.options.virtual ??= {};
189
177
  app.options.virtual["#nitro-internal-virtual/module-config"] = () => {
190
- const moduleConfig = app.options.graphql || {};
178
+ const moduleConfig = {
179
+ ...app.options.graphql,
180
+ security: app.options.runtimeConfig.graphql?.security
181
+ };
191
182
  return `export const moduleConfig = ${JSON.stringify(moduleConfig, null, 2)};`;
192
183
  };
193
184
  }
@@ -1,6 +1,12 @@
1
- import * as h31 from "h3";
1
+ import * as h33 from "h3";
2
2
 
3
3
  //#region src/routes/apollo-server-ws.d.ts
4
- declare const _default: h31.EventHandler<h31.EventHandlerRequest, never>;
4
+
5
+ /**
6
+ * Gracefully shutdown all WebSocket connections.
7
+ * Called by the Nitro runtime plugin on server close.
8
+ */
9
+ declare function shutdownAllConnections(): Promise<void>;
10
+ declare const _default: h33.EventHandler<h33.EventHandlerRequest, never>;
5
11
  //#endregion
6
- export { _default as default };
12
+ export { _default as default, shutdownAllConnections };
@@ -13,6 +13,34 @@ const isDev = process.env.NODE_ENV === "development";
13
13
  function devLog(message, ...args) {
14
14
  if (isDev) console.log(message, ...args);
15
15
  }
16
+ const WS_STATE_KEY = "__nitro_graphql_ws_apollo__";
17
+ function getWsState() {
18
+ if (!globalThis[WS_STATE_KEY]) globalThis[WS_STATE_KEY] = {
19
+ activePeers: /* @__PURE__ */ new Set(),
20
+ cleanupFns: /* @__PURE__ */ new Map()
21
+ };
22
+ return globalThis[WS_STATE_KEY];
23
+ }
24
+ /**
25
+ * Gracefully shutdown all WebSocket connections.
26
+ * Called by the Nitro runtime plugin on server close.
27
+ */
28
+ async function shutdownAllConnections() {
29
+ const state = getWsState();
30
+ const peers = Array.from(state.activePeers);
31
+ if (peers.length > 0) devLog(`[Apollo WS] Shutting down ${peers.length} connection(s)...`);
32
+ for (const peer of peers) {
33
+ const cleanup = state.cleanupFns.get(peer);
34
+ if (cleanup) try {
35
+ await cleanup();
36
+ } catch {}
37
+ try {
38
+ peer.close(1012, "Service Restart");
39
+ } catch {}
40
+ }
41
+ state.activePeers.clear();
42
+ state.cleanupFns.clear();
43
+ }
16
44
  const DEFAULT_MAX_SUBSCRIPTIONS_PER_PEER = 20;
17
45
  const DEFAULT_PING_INTERVAL_MS = 15e3;
18
46
  const DEFAULT_PONG_TIMEOUT_MS = 5e3;
@@ -250,6 +278,12 @@ async function getSchema() {
250
278
  }
251
279
  var apollo_server_ws_default = defineWebSocketHandler({
252
280
  async open(peer) {
281
+ const state = getWsState();
282
+ state.activePeers.add(peer);
283
+ state.cleanupFns.set(peer, async () => {
284
+ stopKeepAlive(peer);
285
+ await cleanupSubscriptions(peer, true);
286
+ });
253
287
  devLog("[Apollo WS] Client connected");
254
288
  peer.context.subscriptions = /* @__PURE__ */ new Map();
255
289
  },
@@ -285,6 +319,9 @@ var apollo_server_ws_default = defineWebSocketHandler({
285
319
  }
286
320
  },
287
321
  async close(peer, details) {
322
+ const state = getWsState();
323
+ state.activePeers.delete(peer);
324
+ state.cleanupFns.delete(peer);
288
325
  devLog("[Apollo WS] Client disconnected:", details);
289
326
  stopKeepAlive(peer);
290
327
  await cleanupSubscriptions(peer, true);
@@ -295,4 +332,4 @@ var apollo_server_ws_default = defineWebSocketHandler({
295
332
  });
296
333
 
297
334
  //#endregion
298
- export { apollo_server_ws_default as default };
335
+ export { apollo_server_ws_default as default, shutdownAllConnections };
@@ -1,6 +1,6 @@
1
- import * as h35 from "h3";
1
+ import * as h39 from "h3";
2
2
 
3
3
  //#region src/routes/apollo-server.d.ts
4
- declare const _default: h35.EventHandler<h35.EventHandlerRequest, Promise<any>>;
4
+ declare const _default: h39.EventHandler<h39.EventHandlerRequest, Promise<any>>;
5
5
  //#endregion
6
6
  export { _default as default };
@@ -10,6 +10,7 @@ import { schemas } from "#nitro-internal-virtual/server-schemas";
10
10
  import { makeExecutableSchema } from "@graphql-tools/schema";
11
11
  import { defineEventHandler } from "h3";
12
12
  import { ApolloServer } from "@apollo/server";
13
+ import { ApolloServerPluginLandingPageDisabled } from "@apollo/server/plugin/disabled";
13
14
  import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
14
15
  import { startServerAndCreateH3Handler } from "nitro-graphql/utils/apollo";
15
16
 
@@ -64,10 +65,34 @@ let apolloServer = null;
64
65
  let serverStarted = false;
65
66
  async function createApolloServer() {
66
67
  if (!apolloServer) {
67
- apolloServer = new ApolloServer(defu({
68
- schema: await createMergedSchema(),
68
+ const schema = await createMergedSchema();
69
+ const securityConfig = moduleConfig.security || {
69
70
  introspection: true,
70
- plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })]
71
+ playground: true,
72
+ maskErrors: false,
73
+ disableSuggestions: false
74
+ };
75
+ const plugins = [];
76
+ if (securityConfig.playground) plugins.push(ApolloServerPluginLandingPageLocalDefault({ embed: true }));
77
+ else plugins.push(ApolloServerPluginLandingPageDisabled());
78
+ apolloServer = new ApolloServer(defu({
79
+ schema,
80
+ introspection: securityConfig.introspection,
81
+ plugins,
82
+ formatError: securityConfig.maskErrors ? (formattedError, _error) => {
83
+ const code = formattedError?.extensions?.code;
84
+ if (code && [
85
+ "BAD_USER_INPUT",
86
+ "GRAPHQL_VALIDATION_FAILED",
87
+ "UNAUTHENTICATED",
88
+ "FORBIDDEN",
89
+ "BAD_REQUEST"
90
+ ].includes(code)) return formattedError;
91
+ return {
92
+ message: "Internal server error",
93
+ extensions: { code: "INTERNAL_SERVER_ERROR" }
94
+ };
95
+ } : void 0
71
96
  }, importedConfig));
72
97
  if (!serverStarted) {
73
98
  await apolloServer.start();
@@ -1,4 +1,4 @@
1
- import * as h37 from "h3";
1
+ import * as h35 from "h3";
2
2
 
3
3
  //#region src/routes/debug.d.ts
4
4
 
@@ -10,7 +10,7 @@ import * as h37 from "h3";
10
10
  * - /_nitro/graphql/debug - HTML dashboard
11
11
  * - /_nitro/graphql/debug?format=json - JSON API
12
12
  */
13
- declare const _default: h37.EventHandler<h37.EventHandlerRequest, Promise<string | {
13
+ declare const _default: h35.EventHandler<h35.EventHandlerRequest, Promise<string | {
14
14
  timestamp: string;
15
15
  environment: {
16
16
  dev: any;
@@ -1,6 +1,12 @@
1
- import * as h33 from "h3";
1
+ import * as h30 from "h3";
2
2
 
3
3
  //#region src/routes/graphql-yoga-ws.d.ts
4
- declare const _default: h33.EventHandler<h33.EventHandlerRequest, never>;
4
+
5
+ /**
6
+ * Gracefully shutdown all WebSocket connections.
7
+ * Called by the Nitro runtime plugin on server close.
8
+ */
9
+ declare function shutdownAllConnections(): Promise<void>;
10
+ declare const _default: h30.EventHandler<h30.EventHandlerRequest, never>;
5
11
  //#endregion
6
- export { _default as default };
12
+ export { _default as default, shutdownAllConnections };
@@ -13,6 +13,34 @@ const isDev = process.env.NODE_ENV === "development";
13
13
  function devLog(message, ...args) {
14
14
  if (isDev) console.log(message, ...args);
15
15
  }
16
+ const WS_STATE_KEY = "__nitro_graphql_ws_yoga__";
17
+ function getWsState() {
18
+ if (!globalThis[WS_STATE_KEY]) globalThis[WS_STATE_KEY] = {
19
+ activePeers: /* @__PURE__ */ new Set(),
20
+ cleanupFns: /* @__PURE__ */ new Map()
21
+ };
22
+ return globalThis[WS_STATE_KEY];
23
+ }
24
+ /**
25
+ * Gracefully shutdown all WebSocket connections.
26
+ * Called by the Nitro runtime plugin on server close.
27
+ */
28
+ async function shutdownAllConnections() {
29
+ const state = getWsState();
30
+ const peers = Array.from(state.activePeers);
31
+ if (peers.length > 0) devLog(`[GraphQL WS] Shutting down ${peers.length} connection(s)...`);
32
+ for (const peer of peers) {
33
+ const cleanup = state.cleanupFns.get(peer);
34
+ if (cleanup) try {
35
+ await cleanup();
36
+ } catch {}
37
+ try {
38
+ peer.close(1012, "Service Restart");
39
+ } catch {}
40
+ }
41
+ state.activePeers.clear();
42
+ state.cleanupFns.clear();
43
+ }
16
44
  const DEFAULT_MAX_SUBSCRIPTIONS_PER_PEER = 20;
17
45
  const DEFAULT_PING_INTERVAL_MS = 15e3;
18
46
  const DEFAULT_PONG_TIMEOUT_MS = 5e3;
@@ -250,6 +278,12 @@ async function getSchema() {
250
278
  }
251
279
  var graphql_yoga_ws_default = defineWebSocketHandler({
252
280
  async open(peer) {
281
+ const state = getWsState();
282
+ state.activePeers.add(peer);
283
+ state.cleanupFns.set(peer, async () => {
284
+ stopKeepAlive(peer);
285
+ await cleanupSubscriptions(peer, true);
286
+ });
253
287
  devLog("[GraphQL WS] Client connected");
254
288
  peer.context.subscriptions = /* @__PURE__ */ new Map();
255
289
  },
@@ -285,6 +319,9 @@ var graphql_yoga_ws_default = defineWebSocketHandler({
285
319
  }
286
320
  },
287
321
  async close(peer, details) {
322
+ const state = getWsState();
323
+ state.activePeers.delete(peer);
324
+ state.cleanupFns.delete(peer);
288
325
  devLog("[GraphQL WS] Client disconnected:", details);
289
326
  stopKeepAlive(peer);
290
327
  await cleanupSubscriptions(peer, true);
@@ -295,4 +332,4 @@ var graphql_yoga_ws_default = defineWebSocketHandler({
295
332
  });
296
333
 
297
334
  //#endregion
298
- export { graphql_yoga_ws_default as default };
335
+ export { graphql_yoga_ws_default as default, shutdownAllConnections };
@@ -1,6 +1,6 @@
1
- import * as h30 from "h3";
1
+ import * as h31 from "h3";
2
2
 
3
3
  //#region src/routes/graphql-yoga.d.ts
4
- declare const _default: h30.EventHandler<h30.EventHandlerRequest, Promise<Response>>;
4
+ declare const _default: h31.EventHandler<h31.EventHandlerRequest, Promise<Response>>;
5
5
  //#endregion
6
6
  export { _default as default };
@@ -77,12 +77,23 @@ async function createMergedSchema() {
77
77
  }
78
78
  let yoga;
79
79
  var graphql_yoga_default = defineEventHandler(async (event) => {
80
- if (!yoga) yoga = createYoga(defu({
81
- schema: await createMergedSchema(),
82
- graphqlEndpoint: "/api/graphql",
83
- landingPage: false,
84
- renderGraphiQL: () => apolloSandboxHtml
85
- }, importedConfig));
80
+ if (!yoga) {
81
+ const schema = await createMergedSchema();
82
+ const securityConfig = moduleConfig.security || {
83
+ introspection: true,
84
+ playground: true,
85
+ maskErrors: false,
86
+ disableSuggestions: false
87
+ };
88
+ yoga = createYoga(defu({
89
+ schema,
90
+ graphqlEndpoint: "/api/graphql",
91
+ landingPage: securityConfig.playground,
92
+ graphiql: securityConfig.playground ? { defaultQuery: "# Welcome to the GraphQL Playground" } : false,
93
+ renderGraphiQL: securityConfig.playground ? () => apolloSandboxHtml : void 0,
94
+ maskedErrors: securityConfig.maskErrors
95
+ }, importedConfig));
96
+ }
86
97
  const request = toWebRequest(event);
87
98
  const response = await yoga.handleRequest(request, event);
88
99
  return new Response(response.body, response);
@@ -1,7 +1,7 @@
1
- import * as h39 from "h3";
1
+ import * as h37 from "h3";
2
2
 
3
3
  //#region src/routes/health.d.ts
4
- declare const _default: h39.EventHandler<h39.EventHandlerRequest, Promise<{
4
+ declare const _default: h37.EventHandler<h37.EventHandlerRequest, Promise<{
5
5
  status: string;
6
6
  message: string;
7
7
  timestamp: string;
@@ -0,0 +1,11 @@
1
+ import * as nitropack0 from "nitropack";
2
+
3
+ //#region src/routes/ws-shutdown.d.ts
4
+
5
+ /**
6
+ * Nitro runtime plugin for graceful WebSocket shutdown.
7
+ * Registered when subscriptions are enabled.
8
+ */
9
+ declare const _default: nitropack0.NitroAppPlugin;
10
+ //#endregion
11
+ export { _default as default };
@@ -0,0 +1,22 @@
1
+ import { defineNitroPlugin } from "nitropack/runtime";
2
+
3
+ //#region src/routes/ws-shutdown.ts
4
+ /**
5
+ * Nitro runtime plugin for graceful WebSocket shutdown.
6
+ * Registered when subscriptions are enabled.
7
+ */
8
+ var ws_shutdown_default = defineNitroPlugin((nitroApp) => {
9
+ nitroApp.hooks.hook("close", async () => {
10
+ if (globalThis.__nitro_graphql_ws_yoga__?.activePeers?.size > 0) try {
11
+ const { shutdownAllConnections } = await import("./graphql-yoga-ws.mjs");
12
+ await shutdownAllConnections();
13
+ } catch {}
14
+ if (globalThis.__nitro_graphql_ws_apollo__?.activePeers?.size > 0) try {
15
+ const { shutdownAllConnections } = await import("./apollo-server-ws.mjs");
16
+ await shutdownAllConnections();
17
+ } catch {}
18
+ });
19
+ });
20
+
21
+ //#endregion
22
+ export { ws_shutdown_default as default };
@@ -16,6 +16,13 @@ type GenericSdkConfig = Omit<Parameters<typeof plugin$1>[2], 'documentMode'> & {
16
16
  };
17
17
  type CodegenClientConfig = TypeScriptPluginConfig & TypeScriptDocumentsPluginConfig & {
18
18
  endpoint?: string;
19
+ /**
20
+ * Generate TypedDocumentNode exports for urql/Apollo Client compatibility.
21
+ * When enabled, generates typed document constants that can be used with
22
+ * any GraphQL client that supports TypedDocumentNode.
23
+ * @default false
24
+ */
25
+ typedDocumentNode?: boolean;
19
26
  };
20
27
  interface IESMImport {
21
28
  name: string;
@@ -198,6 +205,34 @@ interface SubscriptionsConfig {
198
205
  /** WebSocket protocol (currently only graphql-ws is supported) */
199
206
  protocol?: 'graphql-ws';
200
207
  }
208
+ /**
209
+ * Security configuration for production environments
210
+ * All options auto-detect based on NODE_ENV when not explicitly set
211
+ */
212
+ interface SecurityConfig {
213
+ /**
214
+ * Enable GraphQL introspection queries
215
+ * @default true in development, false in production
216
+ */
217
+ introspection?: boolean;
218
+ /**
219
+ * Enable GraphQL playground/sandbox UI
220
+ * @default true in development, false in production
221
+ */
222
+ playground?: boolean;
223
+ /**
224
+ * Mask internal error details in responses
225
+ * When enabled, internal errors show "Internal server error" instead of actual message
226
+ * @default false in development, true in production
227
+ */
228
+ maskErrors?: boolean;
229
+ /**
230
+ * Disable "Did you mean X?" field suggestions in error messages
231
+ * Prevents attackers from discovering field names via brute force
232
+ * @default false in development, true in production
233
+ */
234
+ disableSuggestions?: boolean;
235
+ }
201
236
  interface NitroGraphQLOptions {
202
237
  framework: 'graphql-yoga' | 'apollo-server';
203
238
  endpoint?: {
@@ -256,6 +291,11 @@ interface NitroGraphQLOptions {
256
291
  * Customize base directories for file generation
257
292
  */
258
293
  paths?: PathsConfig;
294
+ /**
295
+ * Security configuration for production environments
296
+ * Auto-detects NODE_ENV and applies secure defaults in production
297
+ */
298
+ security?: SecurityConfig;
259
299
  }
260
300
  //#endregion
261
- export { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, SubscriptionsConfig, TypesConfig };
301
+ export { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, SecurityConfig, SubscriptionsConfig, TypesConfig };
@@ -7,9 +7,10 @@ import { printSchemaWithDirectives } from "@graphql-tools/utils";
7
7
  import { createHash } from "node:crypto";
8
8
  import { codegen } from "@graphql-codegen/core";
9
9
  import { preset } from "@graphql-codegen/import-types-preset";
10
- import { plugin } from "@graphql-codegen/typescript";
11
- import { plugin as plugin$1 } from "@graphql-codegen/typescript-generic-sdk";
12
- import { plugin as plugin$2 } from "@graphql-codegen/typescript-operations";
10
+ import { plugin } from "@graphql-codegen/typed-document-node";
11
+ import { plugin as plugin$1 } from "@graphql-codegen/typescript";
12
+ import { plugin as plugin$2 } from "@graphql-codegen/typescript-generic-sdk";
13
+ import { plugin as plugin$3 } from "@graphql-codegen/typescript-operations";
13
14
  import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
14
15
  import { loadDocuments, loadSchemaSync } from "@graphql-tools/load";
15
16
  import { UrlLoader } from "@graphql-tools/url-loader";
@@ -207,7 +208,7 @@ async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, ou
207
208
  plugins: [{ pluginContent: {} }, { typescript: {} }],
208
209
  pluginMap: {
209
210
  pluginContent: { plugin: pluginContent },
210
- typescript: { plugin }
211
+ typescript: { plugin: plugin$1 }
211
212
  }
212
213
  }),
213
214
  sdk: `// THIS FILE IS GENERATED, DO NOT EDIT!
@@ -236,21 +237,28 @@ export function getSdk(requester: Requester): Sdk {
236
237
  }
237
238
  `
238
239
  };
240
+ const enableTypedDocumentNode = config.typedDocumentNode === true;
241
+ const plugins = [
242
+ { pluginContent: {} },
243
+ { typescript: {} },
244
+ { typescriptOperations: {} }
245
+ ];
246
+ const pluginMap = {
247
+ pluginContent: { plugin: pluginContent },
248
+ typescript: { plugin: plugin$1 },
249
+ typescriptOperations: { plugin: plugin$3 }
250
+ };
251
+ if (enableTypedDocumentNode) {
252
+ plugins.push({ typedDocumentNode: {} });
253
+ pluginMap.typedDocumentNode = { plugin };
254
+ }
239
255
  const output = await codegen({
240
256
  filename: outputPath || "client-types.generated.ts",
241
257
  schema: parse(printSchemaWithDirectives(schema)),
242
258
  documents: [...docs],
243
259
  config: mergedConfig,
244
- plugins: [
245
- { pluginContent: {} },
246
- { typescript: {} },
247
- { typescriptOperations: {} }
248
- ],
249
- pluginMap: {
250
- pluginContent: { plugin: pluginContent },
251
- typescript: { plugin },
252
- typescriptOperations: { plugin: plugin$2 }
253
- }
260
+ plugins,
261
+ pluginMap
254
262
  });
255
263
  const typesPath = virtualTypesPath || (serviceName ? `#graphql/client/${serviceName}` : "#graphql/client");
256
264
  const sdkOutput = await preset.buildGeneratesSection({
@@ -262,7 +270,7 @@ export function getSdk(requester: Requester): Sdk {
262
270
  plugins: [{ pluginContent: {} }, { typescriptGenericSdk: {} }],
263
271
  pluginMap: {
264
272
  pluginContent: { plugin: pluginContent },
265
- typescriptGenericSdk: { plugin: plugin$1 }
273
+ typescriptGenericSdk: { plugin: plugin$2 }
266
274
  }
267
275
  });
268
276
  const sdkContent = (await Promise.all(sdkOutput.map(async (config$1) => {
@@ -232,7 +232,6 @@ async function serverTypeGeneration(app) {
232
232
  if (serverTypesPath) {
233
233
  mkdirSync(dirname(serverTypesPath), { recursive: true });
234
234
  writeFileSync(serverTypesPath, data, "utf-8");
235
- consola.success(`[nitro-graphql] Generated server types at: ${serverTypesPath}`);
236
235
  }
237
236
  } catch (error) {
238
237
  consola.error("Server schema generation error:", error);
@@ -293,13 +292,11 @@ async function generateMainClientTypes(nitro) {
293
292
  if (clientTypesPath) {
294
293
  mkdirSync(dirname(clientTypesPath), { recursive: true });
295
294
  writeFileSync(clientTypesPath, types.types, "utf-8");
296
- consola.success(`[nitro-graphql] Generated client types at: ${clientTypesPath}`);
297
295
  }
298
296
  const sdkPath = resolveFilePath(sdkConfig.main, sdkConfig.enabled, true, "{clientGraphql}/default/sdk.ts", placeholders);
299
297
  if (sdkPath) {
300
298
  mkdirSync(dirname(sdkPath), { recursive: true });
301
299
  writeFileSync(sdkPath, types.sdk, "utf-8");
302
- consola.success(`[nitro-graphql] Generated SDK at: ${sdkPath}`);
303
300
  }
304
301
  if (nitro.options.framework?.name === "nuxt") {
305
302
  generateNuxtOfetchClient(nitro, nitro.graphql.clientDir, "default");
@@ -310,7 +307,6 @@ async function generateMainClientTypes(nitro) {
310
307
  async function generateExternalServicesTypes(nitro) {
311
308
  const externalServices = nitro.options.graphql?.externalServices || [];
312
309
  for (const service of externalServices) try {
313
- consola.info(`[graphql:${service.name}] Processing external service`);
314
310
  await downloadAndSaveSchema(service, nitro.options.buildDir);
315
311
  const schema = await loadExternalSchema(service, nitro.options.buildDir);
316
312
  if (!schema) {
@@ -344,16 +340,13 @@ async function generateExternalServicesTypes(nitro) {
344
340
  if (serviceTypesPath) {
345
341
  mkdirSync(dirname(serviceTypesPath), { recursive: true });
346
342
  writeFileSync(serviceTypesPath, types.types, "utf-8");
347
- consola.success(`[graphql:${service.name}] Generated types at: ${serviceTypesPath}`);
348
343
  }
349
344
  const serviceSdkPath = resolveFilePath(service.paths?.sdk ?? sdkConfig.external, sdkConfig.enabled, true, "{clientGraphql}/{serviceName}/sdk.ts", placeholders);
350
345
  if (serviceSdkPath) {
351
346
  mkdirSync(dirname(serviceSdkPath), { recursive: true });
352
347
  writeFileSync(serviceSdkPath, types.sdk, "utf-8");
353
- consola.success(`[graphql:${service.name}] Generated SDK at: ${serviceSdkPath}`);
354
348
  }
355
349
  if (nitro.options.framework?.name === "nuxt") generateExternalOfetchClient(nitro, service, service.endpoint);
356
- consola.success(`[graphql:${service.name}] External service types generated successfully`);
357
350
  } catch (error) {
358
351
  consola.error(`[graphql:${service.name}] External service generation failed:`, error);
359
352
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nitro-graphql",
3
3
  "type": "module",
4
- "version": "1.7.0-beta.5",
4
+ "version": "1.7.1",
5
5
  "description": "GraphQL integration for Nitro",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -55,10 +55,6 @@
55
55
  "./nuxt": {
56
56
  "types": "./dist/ecosystem/nuxt.d.mts",
57
57
  "import": "./dist/ecosystem/nuxt.mjs"
58
- },
59
- "./subscribe": {
60
- "types": "./dist/subscribe/index.d.mts",
61
- "import": "./dist/subscribe/index.mjs"
62
58
  }
63
59
  },
64
60
  "module": "./dist/index.mjs",
@@ -89,10 +85,11 @@
89
85
  "@apollo/subgraph": "^2.12.2",
90
86
  "@graphql-codegen/core": "^5.0.0",
91
87
  "@graphql-codegen/import-types-preset": "^3.0.1",
92
- "@graphql-codegen/typescript": "^5.0.6",
88
+ "@graphql-codegen/typed-document-node": "^6.1.5",
89
+ "@graphql-codegen/typescript": "^5.0.7",
93
90
  "@graphql-codegen/typescript-generic-sdk": "^4.0.2",
94
- "@graphql-codegen/typescript-operations": "^5.0.6",
95
- "@graphql-codegen/typescript-resolvers": "^5.1.4",
91
+ "@graphql-codegen/typescript-operations": "^5.0.7",
92
+ "@graphql-codegen/typescript-resolvers": "^5.1.5",
96
93
  "@graphql-tools/graphql-file-loader": "^8.1.8",
97
94
  "@graphql-tools/load": "^8.1.7",
98
95
  "@graphql-tools/load-files": "^7.0.1",
@@ -107,24 +104,24 @@
107
104
  "graphql-scalars": "^1.25.0",
108
105
  "knitwork": "^1.3.0",
109
106
  "ohash": "^2.0.11",
110
- "oxc-parser": "^0.102.0",
107
+ "oxc-parser": "^0.104.0",
111
108
  "pathe": "^2.0.3",
112
109
  "tinyglobby": "^0.2.15"
113
110
  },
114
111
  "devDependencies": {
115
- "@antfu/eslint-config": "^6.6.1",
112
+ "@antfu/eslint-config": "^6.7.2",
116
113
  "@nuxt/kit": "^4.2.2",
117
114
  "@nuxt/schema": "^4.2.2",
118
- "@types/node": "^24.10.3",
115
+ "@types/node": "^25.0.3",
119
116
  "bumpp": "^10.3.2",
120
117
  "changelogen": "^0.6.2",
121
118
  "crossws": "0.3.5",
122
- "eslint": "^9.39.1",
119
+ "eslint": "^9.39.2",
123
120
  "graphql": "16.11.0",
124
- "graphql-yoga": "^5.17.1",
121
+ "graphql-yoga": "^5.18.0",
125
122
  "h3": "1.15.3",
126
123
  "nitropack": "^2.12.9",
127
- "tsdown": "^0.17.2",
124
+ "tsdown": "^0.18.2",
128
125
  "typescript": "^5.9.3",
129
126
  "vitepress-plugin-llms": "^1.9.3"
130
127
  },
@@ -143,7 +140,6 @@
143
140
  "docs:build": "cd .docs && pnpm install && pnpm build",
144
141
  "docs:preview": "cd .docs && pnpm preview",
145
142
  "lint": "eslint .",
146
- "lint:fix": "eslint . --fix",
147
- "typecheck": "tsc --noEmit"
143
+ "lint:fix": "eslint . --fix"
148
144
  }
149
145
  }