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