@lewebsimple/nuxt-graphql 0.3.5 → 0.5.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.
Files changed (74) hide show
  1. package/README.md +134 -189
  2. package/dist/module.d.mts +74 -22
  3. package/dist/module.json +2 -2
  4. package/dist/module.mjs +408 -381
  5. package/dist/runtime/app/composables/useAsyncGraphQLQuery.d.ts +19 -0
  6. package/dist/runtime/app/composables/useAsyncGraphQLQuery.js +82 -0
  7. package/dist/runtime/app/composables/useGraphQLCache.client.d.ts +10 -7
  8. package/dist/runtime/app/composables/useGraphQLCache.client.js +16 -31
  9. package/dist/runtime/app/composables/useGraphQLMutation.d.ts +10 -18
  10. package/dist/runtime/app/composables/useGraphQLMutation.js +7 -11
  11. package/dist/runtime/app/composables/useGraphQLQuery.d.ts +11 -9
  12. package/dist/runtime/app/composables/useGraphQLQuery.js +20 -78
  13. package/dist/runtime/app/composables/useGraphQLSubscription.client.d.ts +19 -0
  14. package/dist/runtime/app/composables/{useGraphQLSubscription.js → useGraphQLSubscription.client.js} +9 -14
  15. package/dist/runtime/app/lib/cache.d.ts +18 -0
  16. package/dist/runtime/app/lib/cache.js +9 -0
  17. package/dist/runtime/app/lib/execute-http.d.ts +14 -0
  18. package/dist/runtime/app/lib/execute-http.js +8 -0
  19. package/dist/runtime/app/lib/in-flight.d.ts +14 -0
  20. package/dist/runtime/app/lib/{graphql-cache.js → in-flight.js} +2 -7
  21. package/dist/runtime/app/lib/persisted.d.ts +26 -0
  22. package/dist/runtime/app/plugins/graphql-request.d.ts +7 -2
  23. package/dist/runtime/app/plugins/graphql-request.js +13 -14
  24. package/dist/runtime/app/plugins/graphql-sse.client.d.ts +7 -2
  25. package/dist/runtime/server/api/graphql.d.ts +8 -0
  26. package/dist/runtime/server/api/graphql.js +16 -0
  27. package/dist/runtime/server/lib/context.d.ts +10 -0
  28. package/dist/runtime/server/lib/context.js +3 -0
  29. package/dist/runtime/server/lib/default-context.d.ts +5 -0
  30. package/dist/runtime/server/lib/default-context.js +2 -0
  31. package/dist/runtime/server/lib/default-schema.d.ts +7 -0
  32. package/dist/runtime/server/lib/default-schema.js +18 -0
  33. package/dist/runtime/server/lib/execute-schema.d.ts +11 -0
  34. package/dist/runtime/server/lib/execute-schema.js +23 -0
  35. package/dist/runtime/server/lib/remote-executor.d.ts +29 -34
  36. package/dist/runtime/server/lib/remote-executor.js +27 -55
  37. package/dist/runtime/server/lib/schemas.d.ts +14 -0
  38. package/dist/runtime/server/lib/schemas.js +3 -0
  39. package/dist/runtime/server/lib/yoga.d.ts +6 -0
  40. package/dist/runtime/server/lib/yoga.js +20 -0
  41. package/dist/runtime/server/utils/useServerGraphQLMutation.d.ts +16 -8
  42. package/dist/runtime/server/utils/useServerGraphQLMutation.js +12 -11
  43. package/dist/runtime/server/utils/useServerGraphQLQuery.d.ts +16 -3
  44. package/dist/runtime/server/utils/useServerGraphQLQuery.js +16 -4
  45. package/dist/runtime/shared/lib/cache-config.d.ts +42 -0
  46. package/dist/runtime/{app → shared}/lib/cache-config.js +3 -3
  47. package/dist/runtime/shared/lib/error.d.ts +56 -0
  48. package/dist/runtime/shared/lib/error.js +61 -0
  49. package/dist/runtime/shared/lib/headers.d.ts +13 -3
  50. package/dist/runtime/shared/lib/headers.js +10 -35
  51. package/dist/runtime/shared/lib/registry.d.ts +12 -0
  52. package/dist/runtime/shared/lib/registry.js +8 -0
  53. package/dist/runtime/shared/lib/utils.d.ts +1 -0
  54. package/dist/runtime/shared/lib/utils.js +0 -0
  55. package/dist/runtime/{app → shared}/types/nuxt-graphql.d.ts +1 -5
  56. package/dist/types.d.mts +4 -0
  57. package/package.json +19 -33
  58. package/dist/helpers.d.mts +0 -3
  59. package/dist/helpers.mjs +0 -3
  60. package/dist/runtime/app/composables/useGraphQLSubscription.d.ts +0 -17
  61. package/dist/runtime/app/lib/cache-config.d.ts +0 -8
  62. package/dist/runtime/app/lib/graphql-cache.d.ts +0 -7
  63. package/dist/runtime/server/api/yoga-handler.d.ts +0 -2
  64. package/dist/runtime/server/api/yoga-handler.js +0 -42
  65. package/dist/runtime/server/lib/define-graphql-context.d.ts +0 -5
  66. package/dist/runtime/server/lib/define-graphql-context.js +0 -4
  67. package/dist/runtime/server/lib/define-remote-exec-middleware.d.ts +0 -30
  68. package/dist/runtime/server/lib/define-remote-exec-middleware.js +0 -3
  69. package/dist/runtime/server/lib/define-yoga-middleware.d.ts +0 -21
  70. package/dist/runtime/server/lib/define-yoga-middleware.js +0 -3
  71. package/dist/runtime/server/lib/execute-server-graphql.d.ts +0 -7
  72. package/dist/runtime/server/lib/execute-server-graphql.js +0 -34
  73. package/dist/runtime/shared/lib/graphql-error.d.ts +0 -17
  74. package/dist/runtime/shared/lib/graphql-error.js +0 -28
package/README.md CHANGED
@@ -13,12 +13,12 @@ Opinionated Nuxt module that wires a typed GraphQL server + client into your app
13
13
  ## Features
14
14
 
15
15
  - 🧘 **GraphQL Yoga** server at `/api/graphql` (**GraphiQL** in dev) + **SSE subscriptions**
16
- - 🪡 **Stitched schema** from local and/or remote schemas (remote introspection at build time)
17
- - 🪄 Code generation from `.gql` documents → **typed documents nodes** + **Zod** input schemas output
18
- - 🧠 **Type-safe GraphQL helpers** for **queries, mutations, and subscriptions**, driven by **operation names** and shared across **client + server routes**
19
- - 🧊 **SSR-friendly** by default: request header/cookie forwarding + no-HTTP server execution helpers
20
- - 🚀 **Query caching** for `useGraphQLQuery` (cache policies + optional persistence in localStorage)
21
- - 🪝 **Optional hooks**: Yoga middleware + per-remote executor middleware + client error hook (`graphql:error`)
16
+ - 🪡 **Stitched schema** from local and/or remote schemas (remote introspection at build time; subscriptions stripped)
17
+ - 🪄 Code generation from `.gql` documents → **typed operation documents** + **registry**
18
+ - 🧠 **Type-safe helpers** for **queries, mutations, and subscriptions**, shared across **client + server**
19
+ - 🧊 **SSR-friendly** by default: request header forwarding + server-side schema execution helpers
20
+ - 🚀 **Client-side cache** for `useAsyncGraphQLQuery` (cache policies + optional persistence in localStorage)
21
+ - 🧯 **Unified error model** via `SafeResult` and `NormalizedError`
22
22
 
23
23
 
24
24
  ## Getting started
@@ -26,80 +26,83 @@ Opinionated Nuxt module that wires a typed GraphQL server + client into your app
26
26
  Install the module to your Nuxt application with one command:
27
27
 
28
28
  ```bash
29
- pnpx nuxi module add @lewebsimple/nuxt-graphql
29
+ pnpx nuxt module add @lewebsimple/nuxt-graphql
30
30
  ```
31
31
 
32
32
 
33
33
  ### Configuration
34
34
 
35
- Declare your schemas, context, documents glob and any optional middleware in `nuxt.config.ts`:
35
+ Declare your schemas, context, documents glob and optional client cache in [nuxt.config.ts](nuxt.config.ts):
36
36
 
37
37
  ```ts
38
38
  export default defineNuxtConfig({
39
39
  modules: ["@lewebsimple/nuxt-graphql"],
40
40
  graphql: {
41
- // Schemas to stitch together (local and/or remote)
42
- schemas: {
43
- local: {
44
- type: "local",
45
- path: "server/graphql/schema.ts",
46
- },
47
- // Remote schema example
48
- swapi: {
49
- type: "remote",
50
- url: "https://swapi-graphql.netlify.app/graphql",
51
- // Optional: static headers for this remote
52
- headers: {
53
- "X-Static-Header": "static-header-value",
41
+ yoga: {
42
+ // Schemas to stitch together (local and/or remote)
43
+ schemas: {
44
+ local: {
45
+ type: "local",
46
+ path: "server/graphql/schema.ts",
47
+ },
48
+ // Remote schema example
49
+ swapi: {
50
+ type: "remote",
51
+ url: "https://swapi-graphql.netlify.app/graphql",
52
+ // Optional: static headers for this remote
53
+ headers: {
54
+ "X-Static-Header": "static-header-value",
55
+ },
56
+ // Optional: per-remote execution hooks
57
+ hooks: ["server/graphql/swapi-hooks.ts"],
54
58
  },
55
- // Optional: per-remote execution middleware (onRequest / onResponse / onError hooks)
56
- middleware: "server/graphql/swapi-middleware.ts",
57
59
  },
60
+
61
+ // Optional: custom GraphQL context factories (defaults to [])
62
+ context: ["server/graphql/context.ts"],
58
63
  },
59
-
60
- // Optional: custom GraphQL context (defaults to {})
61
- context: "server/graphql/context.ts",
62
-
63
- // Optional: documents glob (defaults to **/*.gql)
64
- documents: "**/*.gql",
65
-
66
- // Optional: Yoga middleware (onRequest / onResponse hooks)
67
- middleware: "server/graphql/yoga-middleware.ts",
68
-
69
- // Optional: query caching (client-side only)
70
- // - In-memory cache uses Nuxt `useAsyncData`/`useNuxtData`
71
- // - Persistence in localStorage is enabled when `ttl` is set
72
- // - Version / prefix allow cache invalidation accross deployments
73
- cache: {
74
- cachePolicy: "cache-first", // "no-cache" | "cache-first" | "network-first" | "swr"
75
- cacheVersion: "1",
76
- keyPrefix: "gql",
77
- // Persist cache entries in localStorage with TTL in seconds
78
- // - 0 = never expires
79
- // - undefined = persistence disabled
80
- ttl: 60,
64
+
65
+ client: {
66
+ // Optional: documents glob (defaults to **/*.gql)
67
+ documents: "**/*.gql",
68
+
69
+ // Optional: headers forwarded from SSR to graphql-request (defaults to ["authorization", "cookie"])
70
+ ssrForwardHeaders: ["authorization", "cookie"],
71
+
72
+ // Optional: query caching (client-side only, for useAsyncGraphQLQuery)
73
+ cache: {
74
+ policy: "cache-first", // "no-cache" | "cache-first" | "network-first" | "swr"
75
+ keyVersion: "1",
76
+ keyPrefix: "gql",
77
+ // Persist cache entries in localStorage with TTL in seconds
78
+ // - 0 = never expires
79
+ // - undefined = persistence disabled
80
+ ttl: 60,
81
+ },
81
82
  },
82
83
 
83
- // Optional: save path for the generated stitched SDL (defaults to .nuxt/graphql/schema.graphql)
84
- sdl: "server/graphql/schema.graphql",
84
+ // Optional: save path for the stitched SDL (defaults to "server/graphql/schema.graphql")
85
+ saveSDL: "server/graphql/schema.graphql",
86
+
87
+ // Optional: save path for the generated GraphQL config (defaults to "graphql.config.json")
88
+ saveConfig: "graphql.config.json",
85
89
  },
86
90
  });
87
91
  ```
88
92
 
89
93
 
90
- ### Define your schema(s) (local and/or remote)
94
+ ### Define schema(s) (local and/or remote)
91
95
 
92
- **Local schemas** must be located inside `server/` and export an executable `GraphQLSchema` using the tool of your choice (graphql-yoga, Pothos, etc).
96
+ **Local schemas** must live under `server/` and export a `GraphQLSchema` as `schema`. You can use the provided `defineGraphQLSchema` helper for type-safety.
93
97
 
94
- ⚠️ Using auto-imported utilities or importing from aliases might break code generation.
95
-
96
- For the example configuration above, create `server/graphql/schema.ts`:
98
+ For the example configuration above, create [server/graphql/schema.ts](server/graphql/schema.ts):
97
99
 
98
100
  ```ts
99
101
  import { createSchema } from "graphql-yoga";
102
+ import { defineGraphQLSchema } from "@lewebsimple/nuxt-graphql";
100
103
  import type { GraphQLContext } from "#graphql/context";
101
104
 
102
- export const schema = createSchema<GraphQLContext>({
105
+ const schema = createSchema<GraphQLContext>({
103
106
  typeDefs: /* GraphQL */ `
104
107
  type Query {
105
108
  hello: String!
@@ -130,19 +133,21 @@ export const schema = createSchema<GraphQLContext>({
130
133
  },
131
134
  },
132
135
  });
136
+
137
+ export default defineGraphQLSchema({ schema });
133
138
  ```
134
139
 
135
- **Remote schemas** are introspected at build time from the required endpoint URL. Each remote can be configured with optional static headers and remote execution middleware (see configuration above).
140
+ **Remote schemas** are introspected at build time from the required endpoint URL and executed via an HTTP executor at runtime. Subscriptions are stripped from remote schemas.
136
141
 
137
142
 
138
- ### Define a type-safe GraphQL context (optional)
143
+ ### Define GraphQL context factories (optional)
139
144
 
140
- The GraphQL context can be user-defined with the provided `defineGraphQLContext` helper. This ensures proper server-side typing by inferring the type from the return value of the context creation function. The `GraphQLContext` type can be imported from the `#graphql/context` server alias.
145
+ Context factories are optional and run on the server in order. Their return types are merged into a single `GraphQLContext` type.
141
146
 
142
- For example, providing the user from the `nuxt-auth-utils` session with the configuration above, create `server/graphql/context.ts`:
147
+ For example, create [server/graphql/context.ts](server/graphql/context.ts):
143
148
 
144
149
  ```ts
145
- import { defineGraphQLContext } from "@lewebsimple/nuxt-graphql/helpers";
150
+ import { defineGraphQLContext } from "@lewebsimple/nuxt-graphql";
146
151
  import { getUserSession } from "nuxt-auth-utils";
147
152
 
148
153
  export default defineGraphQLContext(async (event) => {
@@ -154,20 +159,20 @@ export default defineGraphQLContext(async (event) => {
154
159
  ```
155
160
 
156
161
 
157
- ### Write GraphQL documents (`.gql`)
162
+ ### Write GraphQL documents (.gql)
158
163
 
159
- Write operations in `.gql` document files; operation names become registry keys like `useGraphQLQuery("HelloWorld")`.
164
+ Write operations in `.gql` files; operation names become registry keys like `useGraphQLQuery("HelloWorld")`.
160
165
 
161
166
  ⚠️ Operation names are required and must be unique.
162
167
 
163
168
  By default, the module scans `**/*.gql` and generates:
164
169
 
165
- - Typed documents in `#graphql/typed-documents`
166
- - Operation registry by name in `#graphql/registry` (used internally)
167
- - Zod schemas in `#graphql/zod`
168
- - A `graphql.config.json` at the project root (editor tooling)
170
+ - Typed documents and types in virtual modules under the `#graphql/operations` alias (internal)
171
+ - Operation registry in virtual modules under the `#graphql/registry` alias (internal)
172
+ - Fragment types in virtual modules under the `#graphql/fragments` alias
173
+
174
+ Example document files:
169
175
 
170
- Example document files (filenaming convention can vary):
171
176
  ```graphql
172
177
  # app/graphql/HelloWorld.query.gql
173
178
  query HelloWorld {
@@ -191,15 +196,14 @@ subscription Time {
191
196
 
192
197
  That's it! You can now use Nuxt GraphQL in your Nuxt app ✨
193
198
 
199
+
194
200
  ### Fragments
195
201
 
196
202
  Fragments are fully supported and are the recommended way to share selection sets across operations.
197
203
 
198
204
  - Fragment names must be unique across all `.gql` files (duplicates throw during generation).
199
- - Fragments are generated into `#graphql/typed-documents` by GraphQL Codegen:
200
- - a TypeScript type like `TheFilmFragment`
201
- - and a document constant like `TheFilmFragmentDoc`
202
- - Fragments are **not executable by themselves** (GraphQL requires an operation), and they are **not part of the `#graphql/registry`**. The auto-imported composables and server utilities only accept operation names (`query` / `mutation` / `subscription`).
205
+ - Fragment types are re-exported from `#graphql/fragments`.
206
+ - Fragments are not executable by themselves and are not part of the registry.
203
207
 
204
208
  Example with a fragment:
205
209
 
@@ -219,108 +223,92 @@ query SwapiFilms {
219
223
  }
220
224
  ```
221
225
 
222
- From TypeScript, you can also use fragment types explicitly when you need them:
226
+ From TypeScript, you can also use fragment types explicitly when needed:
223
227
 
224
228
  ```ts
225
- import type { TheFilmFragment } from "#graphql/typed-documents";
229
+ import type { TheFilmFragment } from "#graphql/fragments";
226
230
  ```
227
231
 
228
232
 
229
233
  ### Use the auto-imported composables
230
234
 
231
- The auto-imported operation composables allow executing **queries**, **mutations** and **subscriptions** based on their registry name with full type-safety (variables and return value).
235
+ The auto-imported composables allow executing queries, mutations, and subscriptions based on their registry name with full type-safety (variables and return value).
232
236
 
233
237
  ```ts
234
- // Query (useAsyncData under the hood)
235
- const { data, pending, error, refresh } = await useGraphQLQuery(
236
- "HelloWorld", // Registry name, i.e. "query HelloWorld { hello }"
237
- undefined, // Type-safe variables
238
+ // Cached query via useAsyncData
239
+ const { data, pending, error, refresh } = await useAsyncGraphQLQuery(
240
+ "HelloWorld",
241
+ undefined,
238
242
  {
239
- // Custom request headers
240
243
  headers: {
241
- "X-Request-Header": "request-header-value"
244
+ "X-Request-Header": "request-header-value",
242
245
  },
243
- // Additional useAsyncData options
244
- // lazy: true,
245
246
  },
246
247
  );
247
248
 
248
- // Mutation
249
- const { mutate } = useGraphQLMutation("Ping", {
250
- // Custom request headers
251
- headers: {
252
- "X-Request-Header": "request-header-value"
253
- },
254
- });
255
- const { data, pending, error } = await mutate({ message: "Hello from ping mutation!" }, {
256
- // Each `mutate` call may send additional headers
249
+ // Direct HTTP query (SafeResult)
250
+ const { data: queryData, error: queryError } = await useGraphQLQuery("HelloWorld");
251
+
252
+ // Mutation (SafeResult)
253
+ const { mutate, pending: mutationPending } = useGraphQLMutation("Ping", {
257
254
  headers: {
258
- "X-Mutate-Header": "mutate-header-value",
255
+ "X-Request-Header": "request-header-value",
259
256
  },
260
257
  });
258
+ const { data: mutationData, error: mutationError } = await mutate({ message: "Hello!" });
261
259
 
262
260
  // Subscription (client-only, SSE)
263
261
  const { data, error, start, stop } = useGraphQLSubscription("Time");
264
- // data and error are shallowRef
265
262
  ```
266
263
 
264
+
267
265
  ### Use the auto-imported server-side utilities
268
266
 
269
- In server routes, you can execute **queries** and **mutations** directly against the stitched schema (no HTTP roundtrip):
267
+ In server routes, you can execute queries and mutations directly against the stitched schema (no HTTP roundtrip):
270
268
 
271
269
  ```ts
272
270
  export default defineEventHandler(async (event) => {
273
271
  // Server-side GraphQL query example
274
- const { hello } = await useServerGraphQLQuery(event, "HelloWorld", undefined, {
275
- headers: {
276
- "X-Server-Header": "server-header-value",
277
- },
278
- });
272
+ const { data: queryData, error: queryError } = await useServerGraphQLQuery(
273
+ event,
274
+ "HelloWorld",
275
+ );
279
276
 
280
277
  // Server-side GraphQL mutation example
281
- const { mutate } = useServerGraphQLMutation(event, "Ping", {
282
- headers: {
283
- "X-Server-Header": "server-header-value",
284
- },
285
- });
286
- const { ping } = await mutate({ message: hello }, {
287
- headers: {
288
- "X-Mutation-Header": "mutation-header-value",
289
- },
290
- });
278
+ const { data: mutationData } = await useServerGraphQLMutation(
279
+ event,
280
+ "Ping",
281
+ { message: queryData?.hello ?? "fallback" },
282
+ );
291
283
 
292
- return { ping };
284
+ return { queryData, mutationData, queryError };
293
285
  });
294
286
  ```
295
287
 
296
- The function signature is almost identical to the auto-imported composables, except you need to pass `event` as the first argument (and of course queries don't rely on `useAsyncData`).
297
-
298
- Also, resulting data is returned directly from `useServerGraphQLQuery` and `mutate` (throws an error on failure).
288
+ Server helpers return a `SafeResult` in the same format as the client helpers.
299
289
 
300
290
 
301
291
  ### Query caching (client-side only)
302
292
 
303
- `useGraphQLQuery` can cache **query results** based on the global cache configuration (see configuration above) and per-query overrides (see below).
293
+ `useAsyncGraphQLQuery` can cache query results based on the global cache configuration and per-query overrides.
304
294
 
305
- - In-flight requests are **deduplicated** (same operation + variables → one network call).
306
- - **In-memory** cache uses Nuxt `useAsyncData`/`useNuxtData`.
307
- - **Persisted** cache stores entries in `localStorage` for `ttl` seconds (`0` = never expires).
295
+ - In-flight requests are deduplicated (same operation + variables → one network call).
296
+ - In-memory cache uses Nuxt `useAsyncData`/`useNuxtData`.
297
+ - Persisted cache stores entries in localStorage for ttl seconds (0 = never expires).
308
298
 
309
299
  #### Cache policies
310
300
 
311
- - `"no-cache"`: always fetches from the network (still dedupes in-flight).
312
- - `"cache-first"`: returns cached value when present, otherwise fetches.
313
- - `"network-first"`: tries the network first, falls back to cached value on error.
314
- - `"swr"` (stale-while-revalidate): returns cached value immediately (when present) and refreshes in the background.
301
+ - "no-cache": always fetches from the network (still dedupes in-flight).
302
+ - "cache-first": returns cached value when present, otherwise fetches.
303
+ - "network-first": tries the network first, falls back to cached value on error.
304
+ - "swr": returns cached value immediately and refreshes in the background.
315
305
 
316
306
  #### Per-query overrides
317
307
 
318
- Caching configuration can be overridden per-query:
319
-
320
308
  ```ts
321
- const { data } = await useGraphQLQuery("HelloWorld", undefined, {
309
+ const { data } = await useAsyncGraphQLQuery("HelloWorld", undefined, {
322
310
  cache: {
323
- cachePolicy: "network-first",
311
+ policy: "network-first",
324
312
  ttl: undefined, // disable persistence for this call
325
313
  },
326
314
  });
@@ -328,92 +316,49 @@ const { data } = await useGraphQLQuery("HelloWorld", undefined, {
328
316
 
329
317
  #### Manual invalidation
330
318
 
331
- On the client, `useGraphQLCache()` is used to invalidate in-memory and/or persisted entries:
319
+ On the client, `useGraphQLCache()` can invalidate in-memory and persisted entries:
332
320
 
333
321
  ```ts
334
- const { invalidateByKey, invalidateByOperation, invalidateAll } = useGraphQLCache();
322
+ const { invalidate } = useGraphQLCache();
335
323
 
336
324
  // Invalidate a single entry (operation + variables)
337
- await invalidateByKey("HelloWorld", {});
325
+ await invalidate({ operation: "HelloWorld", variables: {} });
338
326
 
339
- // Invalidate all entries for an operation (all variables)
340
- await invalidateByOperation("HelloWorld");
341
-
342
- // Optional: target a specific layer
343
- await invalidateByOperation("HelloWorld", { layer: "persisted" });
327
+ // Invalidate all entries for an operation
328
+ await invalidate({ operation: "HelloWorld" });
344
329
 
345
330
  // Invalidate all entries
346
- await invalidateAll();
347
- ```
348
-
349
-
350
- ### Yoga middleware (optional)
351
-
352
- You can define custom logic around the Yoga event handler by using the provided `defineYogaMiddleware` helper with the following hooks:
353
- - `onRequest` runs before Yoga handles the request;
354
- - `onResponse` runs after and can replace the outgoing `Response` via `setResponse`.
355
-
356
- For the example configuration above, create `server/graphql/yoga-middleware.ts`:
357
-
358
- ```ts
359
- import { defineYogaMiddleware } from "@lewebsimple/nuxt-graphql/helpers";
360
- import { getUserSession } from "nuxt-auth-utils";
361
-
362
- export default defineYogaMiddleware({
363
- async onRequest({ event, context, request }) {
364
- const session = await getUserSession(event);
365
- if (!session?.user) {
366
- throw createError({ statusCode: 401, message: "Unauthorized" });
367
- }
368
- },
369
- async onResponse({ event, context, request, response, setResponse }) {
370
- setHeader(event, "X-Custom-Yoga-Middleware-Response-Header", "my-custom-value");
371
- },
372
- });
331
+ await invalidate();
373
332
  ```
374
333
 
375
- ### Remote executor middleware (optional, per remote)
376
334
 
377
- You can define custom logic around the remote executor (from `@graphql-tools/utils`) for each one of your remote schema by using the provided `defineRemoteExecMiddleware` helper with the following hooks:
378
- - `onRequest` runs before the fetch (headers are mutable);
379
- - `onResponse` runs after an OK response before JSON parsing (cloned response);
380
- - `onError` runs for non-2xx responses, GraphQL errors returned in the payload, JSON parse failures, or network errors (the `response` can be `undefined` in that last case).
335
+ ### Remote executor hooks (optional, per remote)
381
336
 
382
- ⚠️ Remote executor middlewares don't have access to the H3 `event` handled by Yoga since they are executed in the context of the delegated subschema resolution.
337
+ You can define custom logic around the remote executor for each remote schema by using the provided `defineRemoteExecutorHooks` helper.
383
338
 
384
- For the example configuration above, create `server/graphql/swapi-middleware.ts`:
339
+ For the example configuration above, create [server/graphql/swapi-hooks.ts](server/graphql/swapi-hooks.ts):
385
340
 
386
341
  ```ts
387
- import { defineRemoteExecMiddleware } from "@lewebsimple/nuxt-graphql";
388
-
389
- export default defineRemoteExecMiddleware({
390
- onRequest({ remoteName, operationName, context, fetchOptions }) {
391
- console.log(`SWAPI Request Middleware [${remoteName} - ${operationName}]`);
392
- fetchOptions.headers.set("X-Remote-Exec-Request-Header", "custom-value");
342
+ import { defineRemoteExecutorHooks } from "@lewebsimple/nuxt-graphql";
343
+
344
+ export default defineRemoteExecutorHooks({
345
+ onRequest(request) {
346
+ // Dynamically inject headers
347
+ request.extensions ??= {};
348
+ request.extensions.headers = {
349
+ ...request.extensions.headers,
350
+ "X-Remote-Exec-Request-Header": "custom-value",
351
+ };
393
352
  },
394
- onResponse({ remoteName, operationName, context, response }) {
395
- console.log(`SWAPI Response Middleware [${remoteName} - ${operationName}]`);
353
+ onResult(result) {
354
+ console.log("Remote result", result);
396
355
  },
397
- onError({ remoteName, operationName, error }) {
398
- console.error(`SWAPI Error Middleware [${remoteName} - ${operationName}]:`, error);
356
+ onError(error) {
357
+ console.error("Remote error", error);
399
358
  },
400
359
  });
401
360
  ```
402
361
 
403
- ### Client error hook (optional)
404
-
405
- Handle normalized GraphQL errors globally on the client (toast, logging, etc.).
406
-
407
- The client calls the `graphql:error` Nuxt hook when a `graphql-request` response contains errors:
408
-
409
- ```ts
410
- export default defineNuxtPlugin((nuxtApp) => {
411
- nuxtApp.hook("graphql:error", (error) => {
412
- console.error("GraphQL error", error);
413
- });
414
- });
415
- ```
416
-
417
362
 
418
363
  ## Contribution
419
364
 
package/dist/module.d.mts CHANGED
@@ -1,36 +1,88 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { CacheConfig } from '../dist/runtime/app/lib/cache-config.js';
2
+ import { HeadersInput } from '../dist/runtime/shared/lib/headers.js';
3
+ import { CacheConfig } from '../dist/runtime/shared/lib/cache-config.js';
4
+ export { defineGraphQLContext } from '../dist/runtime/server/lib/context.js';
5
+ export { defineRemoteExecutorHooks } from '../dist/runtime/server/lib/remote-executor.js';
3
6
 
4
- /**
5
- * Find multiple files across directories
6
- *
7
- * @param dirs Directories to search in
8
- * @param pattern Glob pattern relative to each directory
9
- * @returns Array of found file paths
10
- */
11
- type GlobPattern = string | string[];
12
-
13
- type LocalSchemaDef = {
7
+ interface LocalSchemaDef {
14
8
  type: "local";
9
+ /**
10
+ * Path to a server-side module exporting
11
+ * `export default defineLocalGraphQLSchema({ schema })`
12
+ * Resolved from rootDir.
13
+ */
15
14
  path: string;
16
- };
17
- type RemoteSchemaDef = {
15
+ }
16
+ interface RemoteSchemaDef {
18
17
  type: "remote";
18
+ /**
19
+ * Remote GraphQL endpoint.
20
+ * Used for build-time introspection and runtime delegation.
21
+ */
19
22
  url: string;
20
- headers?: HeadersInit;
21
- middleware?: string;
22
- };
23
+ /**
24
+ * Static headers applied to all delegated requests.
25
+ * `null` unsets a header.
26
+ */
27
+ headers?: HeadersInput;
28
+ /**
29
+ * Paths to remote execution hook modules.
30
+ * Resolved from rootDir.
31
+ */
32
+ hooks?: string[];
33
+ }
23
34
  type SchemaDef = LocalSchemaDef | RemoteSchemaDef;
24
35
 
25
36
  interface NuxtGraphQLModuleOptions {
26
- schemas: Record<string, SchemaDef>;
27
- context?: string;
28
- documents?: GlobPattern;
37
+ /**
38
+ * Client-side GraphQL configuration (HTTP + cache).
39
+ */
40
+ client?: {
41
+ /**
42
+ * Global cache configuration for queries.
43
+ */
44
+ cache?: Partial<CacheConfig>;
45
+ /**
46
+ * GraphQL documents glob pattern.
47
+ * Default: "**\/*.gql"
48
+ */
49
+ documents?: string;
50
+ /**
51
+ * Headers forwarded from the SSR request to graphql-request.
52
+ * Default: ["authorization", "cookie"]
53
+ */
54
+ ssrForwardHeaders?: string[];
55
+ };
56
+ /**
57
+ * Where to write graphql.config.json.
58
+ * Resolved from rootDir.
59
+ * Default: ./graphql.config.json
60
+ */
29
61
  saveConfig?: string;
30
- saveSdl?: string;
31
- middleware?: string;
32
- cache?: Partial<CacheConfig>;
62
+ /**
63
+ * Where to write the stitched GraphQL SDL.
64
+ * Resolved from rootDir.
65
+ * Default: server/graphql/schema.graphql
66
+ */
67
+ saveSDL?: string;
68
+ /**
69
+ * Server-side GraphQL configuration (Yoga + execution).
70
+ */
71
+ yoga?: {
72
+ /**
73
+ * Paths to GraphQL context factories.
74
+ * Must live in server/.
75
+ * Resolved from rootDir.
76
+ */
77
+ context?: string[];
78
+ /**
79
+ * GraphQL schemas to stitch.
80
+ * Key = schemaName.
81
+ */
82
+ schemas?: Record<string, SchemaDef>;
83
+ };
33
84
  }
85
+
34
86
  declare const _default: _nuxt_schema.NuxtModule<NuxtGraphQLModuleOptions, NuxtGraphQLModuleOptions, false>;
35
87
 
36
88
  export { _default as default };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "name": "@lewebsimple/nuxt-graphql",
2
+ "name": "nuxt-graphql",
3
3
  "configKey": "graphql",
4
- "version": "0.3.5",
4
+ "version": "0.5.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"