@lewebsimple/nuxt-graphql 0.2.2 → 0.3.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.
- package/README.md +361 -55
- package/dist/module.d.mts +33 -21
- package/dist/module.json +1 -1
- package/dist/module.mjs +337 -373
- package/dist/runtime/app/composables/useGraphQLCache.client.d.ts +10 -0
- package/dist/runtime/app/composables/useGraphQLCache.client.js +41 -0
- package/dist/runtime/app/composables/useGraphQLMutation.d.ts +17 -8
- package/dist/runtime/app/composables/useGraphQLMutation.js +10 -9
- package/dist/runtime/app/composables/useGraphQLQuery.d.ts +5 -12
- package/dist/runtime/app/composables/useGraphQLQuery.js +74 -28
- package/dist/runtime/app/composables/useGraphQLSubscription.d.ts +5 -6
- package/dist/runtime/app/composables/useGraphQLSubscription.js +13 -12
- package/dist/runtime/app/lib/graphql-cache.d.ts +7 -0
- package/dist/runtime/app/lib/graphql-cache.js +24 -0
- package/dist/runtime/app/lib/persisted.d.ts +4 -0
- package/dist/runtime/app/lib/persisted.js +70 -0
- package/dist/runtime/app/plugins/graphql-request.d.ts +7 -0
- package/dist/runtime/app/plugins/graphql-request.js +21 -0
- package/dist/runtime/app/plugins/graphql-sse.client.d.ts +7 -0
- package/dist/runtime/app/plugins/graphql-sse.client.js +15 -0
- package/dist/runtime/app/types/nuxt-graphql.d.ts +37 -0
- package/dist/runtime/server/api/yoga-handler.js +42 -0
- package/dist/runtime/server/lib/define-graphql-context.d.ts +5 -0
- package/dist/runtime/server/lib/define-graphql-context.js +4 -0
- package/dist/runtime/server/lib/define-remote-exec-middleware.d.ts +30 -0
- package/dist/runtime/server/lib/define-remote-exec-middleware.js +3 -0
- package/dist/runtime/server/lib/define-yoga-middleware.d.ts +21 -0
- package/dist/runtime/server/lib/define-yoga-middleware.js +3 -0
- package/dist/runtime/server/lib/execute-server-graphql.d.ts +7 -0
- package/dist/runtime/server/lib/execute-server-graphql.js +34 -0
- package/dist/runtime/server/lib/remote-executor.d.ts +35 -0
- package/dist/runtime/server/lib/remote-executor.js +64 -0
- package/dist/runtime/server/tsconfig.json +1 -1
- package/dist/runtime/server/utils/useServerGraphQLMutation.d.ts +8 -14
- package/dist/runtime/server/utils/useServerGraphQLMutation.js +8 -11
- package/dist/runtime/server/utils/useServerGraphQLQuery.d.ts +2 -10
- package/dist/runtime/server/utils/useServerGraphQLQuery.js +3 -4
- package/dist/runtime/shared/lib/graphql-error.d.ts +17 -0
- package/dist/runtime/shared/lib/graphql-error.js +28 -0
- package/dist/runtime/shared/lib/headers.d.ts +3 -0
- package/dist/runtime/shared/lib/headers.js +39 -0
- package/dist/types.d.mts +13 -1
- package/package.json +10 -14
- package/dist/runtime/app/composables/useGraphQLCache.d.ts +0 -10
- package/dist/runtime/app/composables/useGraphQLCache.js +0 -15
- package/dist/runtime/app/plugins/graphql.d.ts +0 -31
- package/dist/runtime/app/plugins/graphql.js +0 -42
- package/dist/runtime/app/utils/graphql-cache.d.ts +0 -36
- package/dist/runtime/app/utils/graphql-cache.js +0 -65
- package/dist/runtime/app/utils/graphql-error.d.ts +0 -12
- package/dist/runtime/app/utils/graphql-error.js +0 -24
- package/dist/runtime/server/api/graphql-handler.js +0 -15
- package/dist/runtime/server/lib/constants.d.ts +0 -1
- package/dist/runtime/server/lib/constants.js +0 -1
- package/dist/runtime/server/lib/create-yoga.d.ts +0 -1
- package/dist/runtime/server/lib/create-yoga.js +0 -17
- package/dist/runtime/server/lib/default-context.d.ts +0 -7
- package/dist/runtime/server/lib/default-context.js +0 -1
- package/dist/runtime/server/utils/graphql-client.d.ts +0 -14
- package/dist/runtime/server/utils/graphql-client.js +0 -14
- package/dist/runtime/server/utils/remote-middleware.d.ts +0 -18
- package/dist/runtime/server/utils/remote-middleware.js +0 -0
- /package/dist/runtime/server/api/{graphql-handler.d.ts → yoga-handler.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -5,23 +5,23 @@
|
|
|
5
5
|
[![License][license-src]][license-href]
|
|
6
6
|
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
7
|
|
|
8
|
-
Opinionated Nuxt module that
|
|
8
|
+
Opinionated Nuxt module that wires a typed GraphQL server + client into your app.
|
|
9
|
+
|
|
10
|
+
[✨ Release Notes](/CHANGELOG.md)
|
|
9
11
|
|
|
10
|
-
- ✨ [Release Notes](/CHANGELOG.md)
|
|
11
|
-
- 🏀 [Online playground](https://stackblitz.com/github/lewebsimple/nuxt-graphql?file=playground%2Fapp.vue)
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
- 🪡
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- 🚀
|
|
21
|
-
-
|
|
22
|
-
|
|
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`)
|
|
22
|
+
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Getting started
|
|
25
25
|
|
|
26
26
|
Install the module to your Nuxt application with one command:
|
|
27
27
|
|
|
@@ -29,85 +29,391 @@ Install the module to your Nuxt application with one command:
|
|
|
29
29
|
pnpx nuxi module add @lewebsimple/nuxt-graphql
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
### Configuration
|
|
34
|
+
|
|
35
|
+
Declare your schemas, context, documents glob and any optional middleware in `nuxt.config.ts`:
|
|
33
36
|
|
|
34
37
|
```ts
|
|
35
|
-
// nuxt.config.ts
|
|
36
38
|
export default defineNuxtConfig({
|
|
37
39
|
modules: ["@lewebsimple/nuxt-graphql"],
|
|
38
40
|
graphql: {
|
|
39
|
-
//
|
|
40
|
-
// Defaults to src/runtime/server/lib/default-context.ts if omitted
|
|
41
|
-
context: "server/graphql/context.ts",
|
|
41
|
+
// Schemas to stitch together (local and/or remote)
|
|
42
42
|
schemas: {
|
|
43
|
-
local: {
|
|
43
|
+
local: {
|
|
44
|
+
type: "local",
|
|
45
|
+
path: "server/graphql/schema.ts",
|
|
46
|
+
},
|
|
47
|
+
// Remote schema example
|
|
44
48
|
swapi: {
|
|
45
49
|
type: "remote",
|
|
46
|
-
url: "https://swapi-graphql.netlify.app
|
|
50
|
+
url: "https://swapi-graphql.netlify.app/graphql",
|
|
51
|
+
// Optional: static headers for this remote
|
|
52
|
+
headers: {
|
|
53
|
+
"X-Static-Header": "static-header-value",
|
|
54
|
+
},
|
|
55
|
+
// Optional: per-remote execution middleware (onRequest / onResponse / onError hooks)
|
|
47
56
|
middleware: "server/graphql/swapi-middleware.ts",
|
|
48
57
|
},
|
|
49
58
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Optional: save path for the generated stitched SDL (defaults to .nuxt/graphql/schema.graphql)
|
|
84
|
+
sdl: "server/graphql/schema.graphql",
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
### Define your schema(s) (local and/or remote)
|
|
91
|
+
|
|
92
|
+
**Local schemas** must be located inside `server/` and export an executable `GraphQLSchema` using the tool of your choice (graphql-yoga, Pothos, etc).
|
|
93
|
+
|
|
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`:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { createSchema } from "graphql-yoga";
|
|
100
|
+
import type { GraphQLContext } from "#graphql/context";
|
|
101
|
+
|
|
102
|
+
export const schema = createSchema<GraphQLContext>({
|
|
103
|
+
typeDefs: /* GraphQL */ `
|
|
104
|
+
type Query {
|
|
105
|
+
hello: String!
|
|
106
|
+
}
|
|
107
|
+
type Mutation {
|
|
108
|
+
ping(message: String!): String!
|
|
109
|
+
}
|
|
110
|
+
type Subscription {
|
|
111
|
+
time: String!
|
|
112
|
+
}
|
|
113
|
+
`,
|
|
114
|
+
resolvers: {
|
|
115
|
+
Query: {
|
|
116
|
+
hello: () => "Hello from Nuxt GraphQL!",
|
|
117
|
+
},
|
|
118
|
+
Mutation: {
|
|
119
|
+
ping: (_parent, args) => `pong: ${args.message}`,
|
|
53
120
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
121
|
+
Subscription: {
|
|
122
|
+
time: {
|
|
123
|
+
subscribe: async function* () {
|
|
124
|
+
while (true) {
|
|
125
|
+
yield { time: new Date().toISOString() };
|
|
126
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
},
|
|
57
130
|
},
|
|
58
131
|
},
|
|
59
132
|
});
|
|
60
133
|
```
|
|
61
134
|
|
|
62
|
-
|
|
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).
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
### Define a type-safe GraphQL context (optional)
|
|
139
|
+
|
|
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.
|
|
141
|
+
|
|
142
|
+
For example, providing the user from the `nuxt-auth-utils` session with the configuration above, create `server/graphql/context.ts`:
|
|
63
143
|
|
|
64
144
|
```ts
|
|
65
|
-
import
|
|
145
|
+
import { defineGraphQLContext } from "@lewebsimple/nuxt-graphql";
|
|
146
|
+
import { getUserSession } from "nuxt-auth-utils";
|
|
147
|
+
|
|
148
|
+
export default defineGraphQLContext(async (event) => {
|
|
149
|
+
const session = await getUserSession(event);
|
|
150
|
+
return {
|
|
151
|
+
user: session?.user ?? null,
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
```
|
|
66
155
|
|
|
67
|
-
|
|
68
|
-
|
|
156
|
+
|
|
157
|
+
### Write GraphQL documents (`.gql`)
|
|
158
|
+
|
|
159
|
+
Write operations in `.gql` document files; operation names become registry keys like `useGraphQLQuery("HelloWorld")`.
|
|
160
|
+
|
|
161
|
+
⚠️ Operation names are required and must be unique.
|
|
162
|
+
|
|
163
|
+
By default, the module scans `**/*.gql` and generates:
|
|
164
|
+
|
|
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)
|
|
169
|
+
|
|
170
|
+
Example document files (filenaming convention can vary):
|
|
171
|
+
```graphql
|
|
172
|
+
# app/graphql/HelloWorld.query.gql
|
|
173
|
+
query HelloWorld {
|
|
174
|
+
hello
|
|
69
175
|
}
|
|
70
176
|
```
|
|
71
177
|
|
|
72
|
-
|
|
178
|
+
```graphql
|
|
179
|
+
# app/graphql/Ping.mutation.gql
|
|
180
|
+
mutation Ping($message: String!) {
|
|
181
|
+
ping(message: $message)
|
|
182
|
+
}
|
|
183
|
+
```
|
|
73
184
|
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
185
|
+
```graphql
|
|
186
|
+
# app/graphql/Time.subscription.gql
|
|
187
|
+
subscription Time {
|
|
188
|
+
time
|
|
189
|
+
}
|
|
78
190
|
```
|
|
79
191
|
|
|
80
192
|
That's it! You can now use Nuxt GraphQL in your Nuxt app ✨
|
|
81
193
|
|
|
82
|
-
|
|
194
|
+
### Fragments
|
|
195
|
+
|
|
196
|
+
Fragments are fully supported and are the recommended way to share selection sets across operations.
|
|
197
|
+
|
|
198
|
+
- 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`).
|
|
83
203
|
|
|
84
|
-
|
|
204
|
+
Example with a fragment:
|
|
205
|
+
|
|
206
|
+
```graphql
|
|
207
|
+
# app/graphql/SwapiFilms.query.gql
|
|
208
|
+
fragment TheFilm on Film {
|
|
209
|
+
title
|
|
210
|
+
releaseDate
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
query SwapiFilms {
|
|
214
|
+
allFilms {
|
|
215
|
+
films {
|
|
216
|
+
...TheFilm
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
From TypeScript, you can also use fragment types explicitly when you need them:
|
|
85
223
|
|
|
86
224
|
```ts
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
225
|
+
import type { TheFilmFragment } from "#graphql/typed-documents";
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
### Use the auto-imported composables
|
|
230
|
+
|
|
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).
|
|
232
|
+
|
|
233
|
+
```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
|
+
{
|
|
239
|
+
// Custom request headers
|
|
240
|
+
headers: {
|
|
241
|
+
"X-Request-Header": "request-header-value"
|
|
242
|
+
},
|
|
243
|
+
// Additional useAsyncData options
|
|
244
|
+
// lazy: true,
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
|
|
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
|
|
257
|
+
headers: {
|
|
258
|
+
"X-Mutate-Header": "mutate-header-value",
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Subscription (client-only, SSE)
|
|
263
|
+
const { data, error, start, stop } = useGraphQLSubscription("Time");
|
|
264
|
+
// data and error are shallowRef
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Use the auto-imported server-side utilities
|
|
268
|
+
|
|
269
|
+
In server routes, you can execute **queries** and **mutations** directly against the stitched schema (no HTTP roundtrip):
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
export default defineEventHandler(async (event) => {
|
|
273
|
+
// Server-side GraphQL query example
|
|
274
|
+
const { hello } = await useServerGraphQLQuery(event, "HelloWorld", undefined, {
|
|
275
|
+
headers: {
|
|
276
|
+
"X-Server-Header": "server-header-value",
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// 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
|
+
});
|
|
291
|
+
|
|
292
|
+
return { ping };
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
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).
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
### Query caching (client-side only)
|
|
302
|
+
|
|
303
|
+
`useGraphQLQuery` can cache **query results** based on the global cache configuration (see configuration above) and per-query overrides (see below).
|
|
304
|
+
|
|
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).
|
|
308
|
+
|
|
309
|
+
#### Cache policies
|
|
310
|
+
|
|
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.
|
|
315
|
+
|
|
316
|
+
#### Per-query overrides
|
|
317
|
+
|
|
318
|
+
Caching configuration can be overridden per-query:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
const { data } = await useGraphQLQuery("HelloWorld", undefined, {
|
|
322
|
+
cache: {
|
|
323
|
+
cachePolicy: "network-first",
|
|
324
|
+
ttl: undefined, // disable persistence for this call
|
|
96
325
|
},
|
|
97
|
-
|
|
98
|
-
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### Manual invalidation
|
|
330
|
+
|
|
331
|
+
On the client, `useGraphQLCache()` is used to invalidate in-memory and/or persisted entries:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const { invalidateByKey, invalidateByOperation, invalidateAll } = useGraphQLCache();
|
|
335
|
+
|
|
336
|
+
// Invalidate a single entry (operation + variables)
|
|
337
|
+
await invalidateByKey("HelloWorld", {});
|
|
338
|
+
|
|
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" });
|
|
344
|
+
|
|
345
|
+
// 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";
|
|
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
|
+
}
|
|
99
368
|
},
|
|
100
|
-
}
|
|
369
|
+
async onResponse({ event, context, request, response, setResponse }) {
|
|
370
|
+
setHeader(event, "X-Custom-Yoga-Middleware-Response-Header", "my-custom-value");
|
|
371
|
+
},
|
|
372
|
+
});
|
|
101
373
|
```
|
|
102
374
|
|
|
103
|
-
|
|
375
|
+
### Remote executor middleware (optional, per remote)
|
|
376
|
+
|
|
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).
|
|
381
|
+
|
|
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.
|
|
104
383
|
|
|
105
|
-
|
|
384
|
+
For the example configuration above, create `server/graphql/swapi-middleware.ts`:
|
|
385
|
+
|
|
386
|
+
```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");
|
|
393
|
+
},
|
|
394
|
+
onResponse({ remoteName, operationName, context, response }) {
|
|
395
|
+
console.log(`SWAPI Response Middleware [${remoteName} - ${operationName}]`);
|
|
396
|
+
},
|
|
397
|
+
onError({ remoteName, operationName, error }) {
|
|
398
|
+
console.error(`SWAPI Error Middleware [${remoteName} - ${operationName}]:`, error);
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
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
|
+
```
|
|
106
416
|
|
|
107
|
-
- Generated artifacts live under `.nuxt/graphql` and `.graphqlrc`; they are rewritten only when contents change.
|
|
108
|
-
- Operations must be **named and unique**; duplicates or unnamed operations fail codegen.
|
|
109
|
-
- SSE subscriptions are client-only; do not call `$graphqlSSE` on the server.
|
|
110
|
-
- Cache defaults come from `runtimeConfig.public.graphql.cache`; pass `cache: false` to per-call options to bypass.
|
|
111
417
|
|
|
112
418
|
## Contribution
|
|
113
419
|
|
|
@@ -151,5 +457,5 @@ Both hooks are optional; return a new `RequestInit` from `onRequest` to override
|
|
|
151
457
|
[license-src]: https://img.shields.io/npm/l/@lewebsimple/nuxt-graphql.svg?style=flat&colorA=020420&colorB=00DC82
|
|
152
458
|
[license-href]: https://npmjs.com/package/@lewebsimple/nuxt-graphql
|
|
153
459
|
|
|
154
|
-
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt
|
|
460
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt
|
|
155
461
|
[nuxt-href]: https://nuxt.com
|
package/dist/module.d.mts
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
|
|
2
|
+
export { defineGraphQLContext } from '../dist/runtime/server/lib/define-graphql-context.js';
|
|
3
|
+
export { defineRemoteExecMiddleware } from '../dist/runtime/server/lib/define-remote-exec-middleware.js';
|
|
4
|
+
export { defineYogaMiddleware } from '../dist/runtime/server/lib/define-yoga-middleware.js';
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Find multiple files across directories
|
|
8
|
+
*
|
|
9
|
+
* @param dirs Directories to search in
|
|
10
|
+
* @param pattern Glob pattern relative to each directory
|
|
11
|
+
* @returns Array of found file paths
|
|
12
|
+
*/
|
|
13
|
+
type GlobPattern = string | string[];
|
|
14
|
+
|
|
15
|
+
type CachePolicy = "no-cache" | "cache-first" | "network-first" | "swr";
|
|
16
|
+
interface CacheConfig {
|
|
17
|
+
cachePolicy: CachePolicy;
|
|
18
|
+
cacheVersion: string;
|
|
19
|
+
keyPrefix: string;
|
|
20
|
+
ttl?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type LocalSchemaDef = {
|
|
5
24
|
type: "local";
|
|
6
25
|
path: string;
|
|
7
26
|
};
|
|
8
|
-
type
|
|
27
|
+
type RemoteSchemaDef = {
|
|
9
28
|
type: "remote";
|
|
10
29
|
url: string;
|
|
11
|
-
headers?:
|
|
30
|
+
headers?: HeadersInit;
|
|
12
31
|
middleware?: string;
|
|
13
32
|
};
|
|
14
|
-
type
|
|
33
|
+
type SchemaDef = LocalSchemaDef | RemoteSchemaDef;
|
|
15
34
|
|
|
16
|
-
interface
|
|
35
|
+
interface NuxtGraphQLModuleOptions {
|
|
36
|
+
schemas: Record<string, SchemaDef>;
|
|
17
37
|
context?: string;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
input: string;
|
|
24
|
-
output: string;
|
|
25
|
-
}>;
|
|
26
|
-
};
|
|
27
|
-
client?: {
|
|
28
|
-
cache?: Partial<GraphQLCacheConfig>;
|
|
29
|
-
headers?: Record<string, string>;
|
|
30
|
-
};
|
|
38
|
+
documents?: GlobPattern;
|
|
39
|
+
saveConfig?: string;
|
|
40
|
+
saveSdl?: string;
|
|
41
|
+
middleware?: string;
|
|
42
|
+
cache?: Partial<CacheConfig>;
|
|
31
43
|
}
|
|
32
|
-
declare const _default: _nuxt_schema.NuxtModule<
|
|
44
|
+
declare const _default: _nuxt_schema.NuxtModule<NuxtGraphQLModuleOptions, NuxtGraphQLModuleOptions, false>;
|
|
33
45
|
|
|
34
46
|
export { _default as default };
|
|
35
|
-
export type {
|
|
47
|
+
export type { NuxtGraphQLModuleOptions };
|