nitro-graphql 1.6.1 → 1.7.0-beta.0

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.
@@ -46,7 +46,10 @@ var nuxt_default = defineNuxtModule({
46
46
  if (externalTypesPath) {
47
47
  const relativePath = relativeWithDot(tsconfigDir, externalTypesPath);
48
48
  options.references.push({ path: relativePath });
49
+ options.tsConfig.compilerOptions ??= {};
50
+ options.tsConfig.compilerOptions.paths ??= {};
49
51
  options.tsConfig.compilerOptions.paths[`#graphql/client/${service.name}`] = [relativePath];
52
+ options.tsConfig.include ??= [];
50
53
  options.tsConfig.include.push(relativePath);
51
54
  }
52
55
  }
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { StandardSchemaV1 } from "./types/standard-schema.mjs";
2
- import { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, TypesConfig } from "./types/index.mjs";
2
+ import { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, SubscriptionsConfig, TypesConfig } from "./types/index.mjs";
3
3
  import * as nitropack0 from "nitropack";
4
4
 
5
5
  //#region src/index.d.ts
6
6
  declare const _default: nitropack0.NitroModule;
7
7
  //#endregion
8
- export { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, StandardSchemaV1, TypesConfig, _default as default };
8
+ export { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, StandardSchemaV1, SubscriptionsConfig, TypesConfig, _default as default };
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import { writeFileIfNotExists } from "./utils/file-generator.mjs";
4
4
  import { getDefaultPaths, getScaffoldConfig, getTypesConfig, resolveFilePath, shouldGenerateScaffold } from "./utils/path-resolver.mjs";
5
5
  import { clientTypeGeneration, serverTypeGeneration } from "./utils/type-generation.mjs";
6
6
  import { rollupConfig } from "./rollup.mjs";
7
+ import { subscribeClientTemplate } from "./templates/subscribe-client.mjs";
7
8
  import { existsSync, mkdirSync } from "node:fs";
8
9
  import { fileURLToPath } from "node:url";
9
10
  import { watch } from "chokidar";
@@ -52,7 +53,8 @@ var src_default = defineNitroModule({
52
53
  nitro.options.runtimeConfig.graphql = defu(nitro.options.runtimeConfig.graphql || {}, {
53
54
  endpoint: {
54
55
  graphql: "/api/graphql",
55
- healthCheck: "/api/graphql/health"
56
+ healthCheck: "/api/graphql/health",
57
+ ws: "/api/graphql/ws"
56
58
  },
57
59
  playground: true
58
60
  });
@@ -182,6 +184,20 @@ var src_default = defineNitroModule({
182
184
  handler: join(runtime, "health"),
183
185
  method: "get"
184
186
  });
187
+ if (nitro.options.graphql?.subscriptions?.enabled) {
188
+ nitro.options.experimental ||= {};
189
+ nitro.options.experimental.websocket = true;
190
+ const wsEndpoint = nitro.options.runtimeConfig.graphql?.endpoint?.ws || nitro.options.graphql?.subscriptions?.endpoint || "/api/graphql/ws";
191
+ if (nitro.options.graphql?.framework === "graphql-yoga") nitro.options.handlers.push({
192
+ route: wsEndpoint,
193
+ handler: join(runtime, "graphql-yoga-ws")
194
+ });
195
+ if (nitro.options.graphql?.framework === "apollo-server") nitro.options.handlers.push({
196
+ route: wsEndpoint,
197
+ handler: join(runtime, "apollo-server-ws")
198
+ });
199
+ consola.info(`[nitro-graphql] WebSocket subscriptions enabled at: ${wsEndpoint}`);
200
+ }
185
201
  if (nitro.options.dev) {
186
202
  nitro.options.handlers.push({
187
203
  route: "/_nitro/graphql/debug",
@@ -323,6 +339,12 @@ declare module 'h3' {
323
339
  consola.warn("nitro-graphql: Found context.d.ts file. Please rename it to context.ts for the new structure.");
324
340
  consola.info("The context file should now be context.ts instead of context.d.ts");
325
341
  }
342
+ if (nitro.options.graphql?.subscriptions?.enabled) {
343
+ if (!existsSync(nitro.graphql.clientDir)) mkdirSync(nitro.graphql.clientDir, { recursive: true });
344
+ const defaultDir = resolve(nitro.graphql.clientDir, "default");
345
+ if (!existsSync(defaultDir)) mkdirSync(defaultDir, { recursive: true });
346
+ writeFileIfNotExists(resolve(defaultDir, "subscribe.ts"), subscribeClientTemplate, "subscribe.ts");
347
+ }
326
348
  } else consola.info("[nitro-graphql] Scaffold file generation is disabled (library mode)");
327
349
  }
328
350
  });
package/dist/rollup.mjs CHANGED
@@ -15,7 +15,7 @@ async function rollupConfig(app) {
15
15
  virtualDebugInfo(app);
16
16
  app.hooks.hook("rollup:before", (nitro, rollupConfig$1) => {
17
17
  rollupConfig$1.plugins = rollupConfig$1.plugins || [];
18
- const { include = /\.(graphql|gql)$/i, exclude, validate = false } = app.options.graphql?.loader || {};
18
+ const { include = /\.(graphql|gql)$/i, exclude, validate: validate$1 = false } = app.options.graphql?.loader || {};
19
19
  if (Array.isArray(rollupConfig$1.plugins)) {
20
20
  rollupConfig$1.plugins.push({
21
21
  name: "nitro-graphql",
@@ -24,7 +24,7 @@ async function rollupConfig(app) {
24
24
  if (!include.test(id)) return null;
25
25
  try {
26
26
  const content = await readFile(id, "utf-8");
27
- if (validate) parse(content);
27
+ if (validate$1) parse(content);
28
28
  return `export default ${JSON.stringify(content)}`;
29
29
  } catch (error) {
30
30
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return null;
@@ -0,0 +1,6 @@
1
+ import * as h30 from "h3";
2
+
3
+ //#region src/routes/apollo-server-ws.d.ts
4
+ declare const _default: h30.EventHandler<h30.EventHandlerRequest, never>;
5
+ //#endregion
6
+ export { _default as default };
@@ -0,0 +1,298 @@
1
+ import { parse, subscribe, validate } from "graphql";
2
+ import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge";
3
+ import { importedConfig } from "#nitro-internal-virtual/graphql-config";
4
+ import { moduleConfig } from "#nitro-internal-virtual/module-config";
5
+ import { directives } from "#nitro-internal-virtual/server-directives";
6
+ import { resolvers } from "#nitro-internal-virtual/server-resolvers";
7
+ import { schemas } from "#nitro-internal-virtual/server-schemas";
8
+ import { makeExecutableSchema } from "@graphql-tools/schema";
9
+ import { defineWebSocketHandler } from "h3";
10
+
11
+ //#region src/routes/apollo-server-ws.ts
12
+ const isDev = process.env.NODE_ENV === "development";
13
+ function devLog(message, ...args) {
14
+ if (isDev) console.log(message, ...args);
15
+ }
16
+ const DEFAULT_MAX_SUBSCRIPTIONS_PER_PEER = 20;
17
+ const DEFAULT_PING_INTERVAL_MS = 15e3;
18
+ const DEFAULT_PONG_TIMEOUT_MS = 5e3;
19
+ function sendMessage(peer, message) {
20
+ peer.send(JSON.stringify(message));
21
+ }
22
+ function sendErrorMessage(peer, id, errors) {
23
+ sendMessage(peer, {
24
+ id,
25
+ type: "error",
26
+ payload: errors
27
+ });
28
+ }
29
+ function sendNextMessage(peer, id, payload) {
30
+ sendMessage(peer, {
31
+ id,
32
+ type: "next",
33
+ payload
34
+ });
35
+ }
36
+ function sendCompleteMessage(peer, id) {
37
+ sendMessage(peer, {
38
+ id,
39
+ type: "complete"
40
+ });
41
+ }
42
+ async function handleConnectionInit(peer, payload) {
43
+ if (payload) peer.context.connectionParams = payload;
44
+ const onConnect = importedConfig?.websocket?.onConnect;
45
+ if (onConnect) try {
46
+ const result = await onConnect({
47
+ connectionParams: payload || {},
48
+ headers: Object.fromEntries(peer.request.headers.entries()),
49
+ peerId: peer.id,
50
+ remoteAddress: peer.remoteAddress
51
+ });
52
+ if (result === false) {
53
+ sendMessage(peer, {
54
+ type: "connection_error",
55
+ payload: { message: "Connection rejected by server" }
56
+ });
57
+ peer.close();
58
+ return;
59
+ }
60
+ if (result && typeof result === "object") peer.context.connectionContext = result;
61
+ } catch (error) {
62
+ sendMessage(peer, {
63
+ type: "connection_error",
64
+ payload: { message: error instanceof Error ? error.message : "Connection validation failed" }
65
+ });
66
+ peer.close();
67
+ return;
68
+ }
69
+ sendMessage(peer, { type: "connection_ack" });
70
+ startKeepAlive(peer);
71
+ }
72
+ function handlePing(peer) {
73
+ sendMessage(peer, { type: "pong" });
74
+ }
75
+ function handlePong(peer) {
76
+ if (peer.context.pongTimeout) {
77
+ clearTimeout(peer.context.pongTimeout);
78
+ peer.context.pongTimeout = null;
79
+ }
80
+ }
81
+ function startKeepAlive(peer) {
82
+ stopKeepAlive(peer);
83
+ peer.context.pingInterval = setInterval(() => {
84
+ sendMessage(peer, { type: "ping" });
85
+ peer.context.pongTimeout = setTimeout(() => {
86
+ devLog("[Apollo WS] Peer did not respond to ping, closing connection");
87
+ peer.close();
88
+ }, DEFAULT_PONG_TIMEOUT_MS);
89
+ }, DEFAULT_PING_INTERVAL_MS);
90
+ }
91
+ function stopKeepAlive(peer) {
92
+ if (peer.context.pingInterval) {
93
+ clearInterval(peer.context.pingInterval);
94
+ peer.context.pingInterval = null;
95
+ }
96
+ if (peer.context.pongTimeout) {
97
+ clearTimeout(peer.context.pongTimeout);
98
+ peer.context.pongTimeout = null;
99
+ }
100
+ }
101
+ async function handleSubscribe(peer, msg, schema) {
102
+ if (!msg.id || !msg.payload) {
103
+ sendErrorMessage(peer, msg.id, [{ message: "Invalid subscribe message" }]);
104
+ return;
105
+ }
106
+ const subscriptions = peer.context.subscriptions;
107
+ const connectionParams = peer.context.connectionParams;
108
+ const connectionContext = peer.context.connectionContext;
109
+ if (subscriptions.has(msg.id)) {
110
+ sendErrorMessage(peer, msg.id, [{ message: `Subscription with ID "${msg.id}" already exists` }]);
111
+ return;
112
+ }
113
+ if (subscriptions.size >= DEFAULT_MAX_SUBSCRIPTIONS_PER_PEER) {
114
+ sendErrorMessage(peer, msg.id, [{ message: `Maximum subscriptions limit (${DEFAULT_MAX_SUBSCRIPTIONS_PER_PEER}) reached` }]);
115
+ return;
116
+ }
117
+ try {
118
+ const { query, variables, operationName } = msg.payload;
119
+ const document = typeof query === "string" ? parse(query) : query;
120
+ const validationErrors = validate(schema, document);
121
+ if (validationErrors.length > 0) {
122
+ sendErrorMessage(peer, msg.id, validationErrors.map((err) => ({
123
+ message: err.message,
124
+ locations: err.locations,
125
+ path: err.path
126
+ })));
127
+ return;
128
+ }
129
+ const result = await subscribe({
130
+ schema,
131
+ document,
132
+ variableValues: variables,
133
+ operationName,
134
+ contextValue: {
135
+ connectionParams,
136
+ ...connectionContext,
137
+ headers: Object.fromEntries(peer.request.headers.entries()),
138
+ authorization: peer.request.headers.get("authorization") || connectionParams?.authorization,
139
+ peerId: peer.id,
140
+ remoteAddress: peer.remoteAddress
141
+ }
142
+ });
143
+ if (Symbol.asyncIterator in result) {
144
+ const abortController = new AbortController();
145
+ const trackedSub = {
146
+ iterator: result,
147
+ abortController
148
+ };
149
+ subscriptions.set(msg.id, trackedSub);
150
+ const iterateSubscription = async () => {
151
+ const subscriptionId = msg.id;
152
+ const signal = abortController.signal;
153
+ try {
154
+ for await (const value of result) {
155
+ if (signal.aborted) break;
156
+ sendNextMessage(peer, subscriptionId, value);
157
+ }
158
+ if (!signal.aborted) sendCompleteMessage(peer, subscriptionId);
159
+ } catch (error) {
160
+ if (signal.aborted) return;
161
+ console.error("[Apollo WS] Subscription error:", error);
162
+ sendErrorMessage(peer, subscriptionId, [{ message: error instanceof Error ? error.message : "Subscription error" }]);
163
+ } finally {
164
+ subscriptions.delete(subscriptionId);
165
+ }
166
+ };
167
+ iterateSubscription();
168
+ } else {
169
+ sendNextMessage(peer, msg.id, result);
170
+ sendCompleteMessage(peer, msg.id);
171
+ }
172
+ } catch (error) {
173
+ console.error("[Apollo WS] Operation error:", error);
174
+ sendErrorMessage(peer, msg.id, [{ message: error instanceof Error ? error.message : "Operation failed" }]);
175
+ }
176
+ }
177
+ async function handleComplete(peer, msg) {
178
+ if (!msg.id) return;
179
+ const subscriptions = peer.context.subscriptions;
180
+ const tracked = subscriptions.get(msg.id);
181
+ if (tracked) {
182
+ tracked.abortController.abort();
183
+ if (typeof tracked.iterator.return === "function") try {
184
+ await tracked.iterator.return();
185
+ } catch {}
186
+ subscriptions.delete(msg.id);
187
+ }
188
+ }
189
+ async function cleanupSubscriptions(peer, sendComplete = false) {
190
+ const subscriptions = peer.context.subscriptions;
191
+ if (!subscriptions) return;
192
+ for (const [id, tracked] of subscriptions.entries()) {
193
+ if (sendComplete) try {
194
+ sendCompleteMessage(peer, id);
195
+ } catch {}
196
+ tracked.abortController.abort();
197
+ if (typeof tracked.iterator.return === "function") try {
198
+ await tracked.iterator.return();
199
+ } catch (error) {
200
+ console.error(`[Apollo WS] Error cleaning up subscription ${id}:`, error);
201
+ }
202
+ }
203
+ subscriptions.clear();
204
+ }
205
+ let buildSubgraphSchema = null;
206
+ async function loadFederationSupport() {
207
+ if (buildSubgraphSchema !== null) return buildSubgraphSchema;
208
+ try {
209
+ buildSubgraphSchema = (await import("@apollo/subgraph")).buildSubgraphSchema;
210
+ } catch {
211
+ buildSubgraphSchema = false;
212
+ }
213
+ return buildSubgraphSchema;
214
+ }
215
+ async function createMergedSchema() {
216
+ const typeDefs = mergeTypeDefs([schemas.map((schema$1) => schema$1.def).join("\n\n")], {
217
+ throwOnConflict: true,
218
+ commentDescriptions: true,
219
+ sort: true
220
+ });
221
+ const mergedResolvers = mergeResolvers(resolvers.map((r) => r.resolver));
222
+ const federationEnabled = moduleConfig.federation?.enabled;
223
+ let schema;
224
+ if (federationEnabled) {
225
+ const buildSubgraph = await loadFederationSupport();
226
+ if (buildSubgraph) schema = buildSubgraph({
227
+ typeDefs: typeof typeDefs === "string" ? parse(typeDefs) : typeDefs,
228
+ resolvers: mergedResolvers
229
+ });
230
+ else {
231
+ console.warn("[Apollo WS] Federation enabled but @apollo/subgraph not available");
232
+ schema = makeExecutableSchema({
233
+ typeDefs,
234
+ resolvers: mergedResolvers
235
+ });
236
+ }
237
+ } else schema = makeExecutableSchema({
238
+ typeDefs,
239
+ resolvers: mergedResolvers
240
+ });
241
+ if (directives && directives.length > 0) {
242
+ for (const { directive } of directives) if (directive.transformer) schema = directive.transformer(schema);
243
+ }
244
+ return schema;
245
+ }
246
+ let schemaPromise = null;
247
+ async function getSchema() {
248
+ if (!schemaPromise) schemaPromise = createMergedSchema();
249
+ return schemaPromise;
250
+ }
251
+ var apollo_server_ws_default = defineWebSocketHandler({
252
+ async open(peer) {
253
+ devLog("[Apollo WS] Client connected");
254
+ peer.context.subscriptions = /* @__PURE__ */ new Map();
255
+ },
256
+ async message(peer, message) {
257
+ try {
258
+ const data = message.text();
259
+ const msg = JSON.parse(data);
260
+ const currentSchema = await getSchema();
261
+ switch (msg.type) {
262
+ case "connection_init":
263
+ await handleConnectionInit(peer, msg.payload);
264
+ break;
265
+ case "ping":
266
+ handlePing(peer);
267
+ break;
268
+ case "pong":
269
+ handlePong(peer);
270
+ break;
271
+ case "subscribe":
272
+ await handleSubscribe(peer, msg, currentSchema);
273
+ break;
274
+ case "complete":
275
+ await handleComplete(peer, msg);
276
+ break;
277
+ default: devLog("[Apollo WS] Unknown message type:", msg.type);
278
+ }
279
+ } catch (error) {
280
+ console.error("[Apollo WS] Message handling error:", error);
281
+ sendMessage(peer, {
282
+ type: "error",
283
+ payload: [{ message: "Invalid message format" }]
284
+ });
285
+ }
286
+ },
287
+ async close(peer, details) {
288
+ devLog("[Apollo WS] Client disconnected:", details);
289
+ stopKeepAlive(peer);
290
+ await cleanupSubscriptions(peer, true);
291
+ },
292
+ async error(_peer, error) {
293
+ console.error("[Apollo WS] WebSocket error:", error);
294
+ }
295
+ });
296
+
297
+ //#endregion
298
+ export { apollo_server_ws_default as default };
@@ -1,6 +1,6 @@
1
- import * as h30 from "h3";
1
+ import * as h31 from "h3";
2
2
 
3
3
  //#region src/routes/apollo-server.d.ts
4
- declare const _default: h30.EventHandler<h30.EventHandlerRequest, Promise<any>>;
4
+ declare const _default: h31.EventHandler<h31.EventHandlerRequest, Promise<any>>;
5
5
  //#endregion
6
6
  export { _default as default };
@@ -7,10 +7,10 @@ import { moduleConfig } from "#nitro-internal-virtual/module-config";
7
7
  import { directives } from "#nitro-internal-virtual/server-directives";
8
8
  import { resolvers } from "#nitro-internal-virtual/server-resolvers";
9
9
  import { schemas } from "#nitro-internal-virtual/server-schemas";
10
- import { ApolloServer } from "@apollo/server";
11
- import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
12
10
  import { makeExecutableSchema } from "@graphql-tools/schema";
13
11
  import { defineEventHandler } from "h3";
12
+ import { ApolloServer } from "@apollo/server";
13
+ import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
14
14
  import { startServerAndCreateH3Handler } from "nitro-graphql/utils/apollo";
15
15
 
16
16
  //#region src/routes/apollo-server.ts
@@ -1,4 +1,4 @@
1
- import * as h31 from "h3";
1
+ import * as h33 from "h3";
2
2
 
3
3
  //#region src/routes/debug.d.ts
4
4
 
@@ -10,7 +10,7 @@ import * as h31 from "h3";
10
10
  * - /_nitro/graphql/debug - HTML dashboard
11
11
  * - /_nitro/graphql/debug?format=json - JSON API
12
12
  */
13
- declare const _default: h31.EventHandler<h31.EventHandlerRequest, Promise<string | {
13
+ declare const _default: h33.EventHandler<h33.EventHandlerRequest, Promise<string | {
14
14
  timestamp: string;
15
15
  environment: {
16
16
  dev: any;
@@ -0,0 +1,6 @@
1
+ import * as h37 from "h3";
2
+
3
+ //#region src/routes/graphql-yoga-ws.d.ts
4
+ declare const _default: h37.EventHandler<h37.EventHandlerRequest, never>;
5
+ //#endregion
6
+ export { _default as default };