nitro-graphql 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +476 -0
- package/dist/client-codegen-DM2n5Gt2.js +86 -0
- package/dist/client-codegen-DM2n5Gt2.js.map +1 -0
- package/dist/client-watcher.d.ts +10 -0
- package/dist/client-watcher.d.ts.map +1 -0
- package/dist/client-watcher.js +92 -0
- package/dist/client-watcher.js.map +1 -0
- package/dist/codegen-DWJuLowd.d.ts +16 -0
- package/dist/codegen-DWJuLowd.d.ts.map +1 -0
- package/dist/codegen-Dbw6gEZt.js +110 -0
- package/dist/codegen-Dbw6gEZt.js.map +1 -0
- package/dist/codegen.d.ts +2 -0
- package/dist/codegen.js +3 -0
- package/dist/context-BgqNJFCT.d.ts +13 -0
- package/dist/context-BgqNJFCT.d.ts.map +1 -0
- package/dist/context-CZdhkJYD.js +0 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +424 -0
- package/dist/index.js.map +1 -0
- package/dist/scanner-BdcKEPQk.js +119 -0
- package/dist/scanner-BdcKEPQk.js.map +1 -0
- package/dist/types-D_NqyCcy.d.ts +47 -0
- package/dist/types-D_NqyCcy.d.ts.map +1 -0
- package/dist/utils-87_22aIA.js +41 -0
- package/dist/utils-87_22aIA.js.map +1 -0
- package/dist/utils-BuYDOLIi.d.ts +22 -0
- package/dist/utils-BuYDOLIi.d.ts.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +3 -0
- package/dist/watcher.d.ts +9 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +96 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +87 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Silgi.js
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# Nitro GraphQL Yoga Module
|
|
2
|
+
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> This project is actively under development. We're always open to new ideas, different perspectives, and feature suggestions! If you have a suggestion, please first [open an issue](https://github.com/productdevbook/nitro-graphql/issues) to discuss it, then you can contribute with a PR.
|
|
5
|
+
|
|
6
|
+
A standalone Nitro module that integrates GraphQL Yoga server into any Nitro application with automatic type generation and file watching.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🚀 Easy GraphQL server setup with GraphQL Yoga
|
|
11
|
+
- 🔧 Auto-discovery of GraphQL schema and resolver files
|
|
12
|
+
- 📝 Automatic TypeScript type generation from GraphQL schemas
|
|
13
|
+
- 🎮 Apollo Sandbox integration (instead of GraphiQL)
|
|
14
|
+
- 🏥 Built-in health check endpoint
|
|
15
|
+
- 💾 Configurable cache headers for better performance
|
|
16
|
+
- 🔌 Works with any Nitro-based application
|
|
17
|
+
- 🎯 Zero-config with sensible defaults
|
|
18
|
+
- 📂 File-based resolver organization
|
|
19
|
+
- 🔄 Hot reload in development mode
|
|
20
|
+
- 📦 Optimized bundle size with dynamic imports
|
|
21
|
+
- 🏷️ Minimal logging with consistent tagging
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install nitro-graphql
|
|
27
|
+
# or
|
|
28
|
+
pnpm add nitro-graphql
|
|
29
|
+
# or
|
|
30
|
+
yarn add nitro-graphql
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### 1. Add the module to your Nitro config
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// nitro.config.ts
|
|
39
|
+
import { defineNitroConfig } from 'nitropack/config'
|
|
40
|
+
|
|
41
|
+
export default defineNitroConfig({
|
|
42
|
+
modules: ['nitro-graphql'],
|
|
43
|
+
|
|
44
|
+
// Optional configuration
|
|
45
|
+
graphqlYoga: {
|
|
46
|
+
endpoint: '/api/graphql', // default
|
|
47
|
+
playground: true, // default (Apollo Sandbox)
|
|
48
|
+
cors: false, // default
|
|
49
|
+
cacheHeaders: {
|
|
50
|
+
enabled: true, // default
|
|
51
|
+
maxAge: 604800, // default (1 week)
|
|
52
|
+
},
|
|
53
|
+
client: {
|
|
54
|
+
enabled: false, // default
|
|
55
|
+
outputPath: undefined, // Will default to .nitro/types/graphql-client.generated.ts
|
|
56
|
+
watchPatterns: undefined, // Will default to src/**/*.{graphql,gql} excluding server/graphql
|
|
57
|
+
config: {
|
|
58
|
+
documentMode: 'string',
|
|
59
|
+
emitLegacyCommonJSImports: false,
|
|
60
|
+
useTypeImports: true,
|
|
61
|
+
enumsAsTypes: true,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Create your GraphQL schema files
|
|
69
|
+
|
|
70
|
+
The module automatically scans for GraphQL files in your `graphql/` directory using a domain-driven structure:
|
|
71
|
+
|
|
72
|
+
#### Main Schema File
|
|
73
|
+
```graphql
|
|
74
|
+
# server/graphql/schema.graphql
|
|
75
|
+
scalar DateTime
|
|
76
|
+
scalar JSON
|
|
77
|
+
|
|
78
|
+
type Query {
|
|
79
|
+
hello: String!
|
|
80
|
+
greeting(name: String!): String!
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type Mutation
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Domain-specific Schema Files
|
|
87
|
+
```graphql
|
|
88
|
+
# server/graphql/users/user.graphql
|
|
89
|
+
type User {
|
|
90
|
+
id: ID!
|
|
91
|
+
name: String!
|
|
92
|
+
email: String!
|
|
93
|
+
createdAt: DateTime!
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
input CreateUserInput {
|
|
97
|
+
name: String!
|
|
98
|
+
email: String!
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
extend type Query {
|
|
102
|
+
users: [User!]!
|
|
103
|
+
user(id: ID!): User
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
extend type Mutation {
|
|
107
|
+
createUser(input: CreateUserInput!): User!
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Domain-based Resolver Files
|
|
112
|
+
```ts
|
|
113
|
+
// server/graphql/users/user-queries.ts
|
|
114
|
+
import { createResolver } from 'nitro-graphql'
|
|
115
|
+
|
|
116
|
+
export default createResolver({
|
|
117
|
+
Query: {
|
|
118
|
+
users: async (_, __, { storage }) => {
|
|
119
|
+
return await storage.getItem('users') || []
|
|
120
|
+
},
|
|
121
|
+
user: async (_, { id }, { storage }) => {
|
|
122
|
+
const users = await storage.getItem('users') || []
|
|
123
|
+
return users.find(user => user.id === id)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// server/graphql/users/create-user.ts
|
|
131
|
+
import { createResolver } from 'nitro-graphql'
|
|
132
|
+
|
|
133
|
+
export default createResolver({
|
|
134
|
+
Mutation: {
|
|
135
|
+
createUser: async (_, { input }, { storage }) => {
|
|
136
|
+
const users = await storage.getItem('users') || []
|
|
137
|
+
const user = {
|
|
138
|
+
id: Date.now().toString(),
|
|
139
|
+
...input,
|
|
140
|
+
createdAt: new Date()
|
|
141
|
+
}
|
|
142
|
+
users.push(user)
|
|
143
|
+
await storage.setItem('users', users)
|
|
144
|
+
return user
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 3. Using the utilities
|
|
151
|
+
|
|
152
|
+
The module provides utilities for better developer experience:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// server/graphql/hello.ts
|
|
156
|
+
import { createResolver } from 'nitro-graphql'
|
|
157
|
+
|
|
158
|
+
export default createResolver({
|
|
159
|
+
Query: {
|
|
160
|
+
hello: () => 'Hello World!',
|
|
161
|
+
greeting: (_, { name }) => `Hello ${name}!`
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 4. Type Generation
|
|
167
|
+
|
|
168
|
+
The module automatically generates TypeScript types from your GraphQL schema:
|
|
169
|
+
|
|
170
|
+
- **Server types**: `.nitro/types/graphql-types.generated.ts`
|
|
171
|
+
- **Type declarations**: `.nitro/types/graphql.d.ts`
|
|
172
|
+
|
|
173
|
+
These types are automatically available in your resolvers:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import type { QueryResolvers } from '#build/graphql-types.generated'
|
|
177
|
+
// server/graphql/users/user-queries.ts
|
|
178
|
+
import { createResolver } from 'nitro-graphql'
|
|
179
|
+
|
|
180
|
+
export default createResolver({
|
|
181
|
+
Query: {
|
|
182
|
+
users: async (_, __, { storage }): Promise<User[]> => {
|
|
183
|
+
return await storage.getItem('users') || []
|
|
184
|
+
}
|
|
185
|
+
} satisfies QueryResolvers
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## File Structure
|
|
190
|
+
|
|
191
|
+
The module follows a domain-driven file structure:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
server/
|
|
195
|
+
├── graphql/
|
|
196
|
+
│ ├── schema.graphql # Main schema file with scalars and base types
|
|
197
|
+
│ ├── hello.ts # Global resolvers
|
|
198
|
+
│ ├── users/
|
|
199
|
+
│ │ ├── user.graphql # User schema definitions
|
|
200
|
+
│ │ ├── user-queries.ts # User query resolvers
|
|
201
|
+
│ │ └── create-user.ts # User mutation resolvers
|
|
202
|
+
│ ├── todos/
|
|
203
|
+
│ │ ├── todo.graphql # Todo schema definitions
|
|
204
|
+
│ │ ├── todo-queries.ts # Todo query resolvers
|
|
205
|
+
│ │ └── todo-mutations.ts # Todo mutation resolvers
|
|
206
|
+
│ ├── posts/
|
|
207
|
+
│ │ ├── post.graphql # Post schema definitions
|
|
208
|
+
│ │ ├── post-queries.ts # Post query resolvers
|
|
209
|
+
│ │ └── create-post.ts # Post mutation resolvers
|
|
210
|
+
│ └── comments/
|
|
211
|
+
│ ├── comment.graphql # Comment schema definitions
|
|
212
|
+
│ ├── comment-queries.ts # Comment query resolvers
|
|
213
|
+
│ └── add-comment.ts # Comment mutation resolvers
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Context
|
|
217
|
+
|
|
218
|
+
The GraphQL context includes:
|
|
219
|
+
- `event`: The H3 event object
|
|
220
|
+
- `request`: The original request object
|
|
221
|
+
- `storage`: Nitro storage instance
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// Example resolver with full context usage
|
|
225
|
+
import { createResolver } from 'nitro-graphql'
|
|
226
|
+
|
|
227
|
+
export default createResolver({
|
|
228
|
+
Query: {
|
|
229
|
+
currentUser: async (_, __, { event, request, storage }) => {
|
|
230
|
+
const token = getCookie(event, 'auth-token')
|
|
231
|
+
const userAgent = getHeader(event, 'user-agent')
|
|
232
|
+
|
|
233
|
+
if (!token) {
|
|
234
|
+
throw new Error('Unauthorized')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const userId = await verifyToken(token)
|
|
238
|
+
return await storage.getItem(`user:${userId}`)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Configuration Options
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
interface NitroGraphQLYogaOptions {
|
|
248
|
+
// GraphQL endpoint path
|
|
249
|
+
endpoint?: string // default: '/api/graphql'
|
|
250
|
+
|
|
251
|
+
// Enable/disable Apollo Sandbox
|
|
252
|
+
playground?: boolean // default: true
|
|
253
|
+
|
|
254
|
+
// CORS configuration
|
|
255
|
+
cors?: boolean | {
|
|
256
|
+
origin?: string | string[] | boolean
|
|
257
|
+
credentials?: boolean
|
|
258
|
+
methods?: string[]
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Cache headers configuration
|
|
262
|
+
cacheHeaders?: {
|
|
263
|
+
enabled?: boolean // default: true
|
|
264
|
+
maxAge?: number // default: 604800 (1 week)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Client type generation
|
|
268
|
+
client?: {
|
|
269
|
+
enabled?: boolean // default: false
|
|
270
|
+
outputPath?: string // default: buildDir/types/graphql-client.generated.ts
|
|
271
|
+
watchPatterns?: string[] // default: src/**/*.{graphql,gql} excluding server/graphql
|
|
272
|
+
config?: {
|
|
273
|
+
documentMode?: 'string' | 'graphQLTag'
|
|
274
|
+
emitLegacyCommonJSImports?: boolean
|
|
275
|
+
useTypeImports?: boolean
|
|
276
|
+
enumsAsTypes?: boolean
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Development Features
|
|
283
|
+
|
|
284
|
+
### Hot Reload
|
|
285
|
+
The module watches for changes in your GraphQL files and automatically:
|
|
286
|
+
- Regenerates TypeScript types
|
|
287
|
+
- Reloads the GraphQL schema
|
|
288
|
+
- Updates resolvers
|
|
289
|
+
|
|
290
|
+
### Bundle Optimization
|
|
291
|
+
- Uses dynamic imports to prevent bundling large codegen dependencies
|
|
292
|
+
- Optimized chunk organization for better caching
|
|
293
|
+
- Minimal logging output with consistent `[graphql]` tagging
|
|
294
|
+
|
|
295
|
+
### Health Check
|
|
296
|
+
Access the health check endpoint at `/api/graphql/health` to verify your GraphQL server status.
|
|
297
|
+
|
|
298
|
+
## Advanced Usage
|
|
299
|
+
|
|
300
|
+
### Custom GraphQL Yoga Configuration
|
|
301
|
+
|
|
302
|
+
You can create a custom GraphQL Yoga configuration file that will be automatically merged with the default configuration. The module will look for this file **only in the `server/graphql/` directory**:
|
|
303
|
+
|
|
304
|
+
- `server/graphql/yoga.config.ts`
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { useCORS } from '@graphql-yoga/plugin-cors'
|
|
308
|
+
import { useResponseCache } from '@graphql-yoga/plugin-response-cache'
|
|
309
|
+
// server/graphql/yoga.config.ts
|
|
310
|
+
import { defineYogaConfig } from 'nitro-graphql'
|
|
311
|
+
|
|
312
|
+
export default defineYogaConfig({
|
|
313
|
+
// Add custom plugins
|
|
314
|
+
plugins: [
|
|
315
|
+
useCORS({
|
|
316
|
+
origin: process.env.NODE_ENV === 'production' ? 'https://yourdomain.com' : '*',
|
|
317
|
+
credentials: true,
|
|
318
|
+
}),
|
|
319
|
+
useResponseCache({
|
|
320
|
+
session: () => null,
|
|
321
|
+
ttl: 60_000, // 1 minute
|
|
322
|
+
}),
|
|
323
|
+
],
|
|
324
|
+
|
|
325
|
+
// Enhanced context with custom properties
|
|
326
|
+
context: async ({ request }) => {
|
|
327
|
+
const event = request.$$event
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
event,
|
|
331
|
+
request,
|
|
332
|
+
storage: useStorage(),
|
|
333
|
+
// Add custom context properties
|
|
334
|
+
user: await authenticateUser(event),
|
|
335
|
+
db: await connectDatabase(),
|
|
336
|
+
startTime: Date.now(),
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// Custom error handling
|
|
341
|
+
maskedErrors: {
|
|
342
|
+
maskError: (error, message, isDev) => {
|
|
343
|
+
if (error.message.startsWith('USER_')) {
|
|
344
|
+
return error // Don't mask user-facing errors
|
|
345
|
+
}
|
|
346
|
+
return isDev ? error : new Error('Internal server error')
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
// Additional yoga options
|
|
351
|
+
// See: https://the-guild.dev/graphql/yoga-server/docs
|
|
352
|
+
})
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Configuration Merging**: Your custom config is merged with the default config, allowing you to override specific options while keeping defaults for others. The `schema` and `graphqlEndpoint` are always preserved from the module's configuration.
|
|
356
|
+
|
|
357
|
+
### Client Type Generation
|
|
358
|
+
|
|
359
|
+
Enable client type generation for your frontend queries:
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
// nitro.config.ts
|
|
363
|
+
export default defineNitroConfig({
|
|
364
|
+
graphqlYoga: {
|
|
365
|
+
client: {
|
|
366
|
+
enabled: true,
|
|
367
|
+
watchPatterns: [
|
|
368
|
+
'client/**/*.graphql',
|
|
369
|
+
'pages/**/*.vue',
|
|
370
|
+
'components/**/*.vue'
|
|
371
|
+
]
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Custom Scalars
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
import { GraphQLScalarType } from 'graphql'
|
|
381
|
+
import { Kind } from 'graphql/language'
|
|
382
|
+
// server/graphql/scalars/DateTime.ts
|
|
383
|
+
import { createResolver } from 'nitro-graphql'
|
|
384
|
+
|
|
385
|
+
export default createResolver({
|
|
386
|
+
DateTime: new GraphQLScalarType({
|
|
387
|
+
name: 'DateTime',
|
|
388
|
+
serialize: (value: Date) => value.toISOString(),
|
|
389
|
+
parseValue: (value: string) => new Date(value),
|
|
390
|
+
parseLiteral: (ast) => {
|
|
391
|
+
if (ast.kind === Kind.STRING) {
|
|
392
|
+
return new Date(ast.value)
|
|
393
|
+
}
|
|
394
|
+
return null
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Error Handling
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
// server/graphql/users/user-queries.ts
|
|
404
|
+
import { createResolver } from 'nitro-graphql'
|
|
405
|
+
|
|
406
|
+
export default createResolver({
|
|
407
|
+
Query: {
|
|
408
|
+
user: async (_, { id }, { storage }) => {
|
|
409
|
+
try {
|
|
410
|
+
const user = await storage.getItem(`user:${id}`)
|
|
411
|
+
if (!user) {
|
|
412
|
+
throw new Error(`User with id ${id} not found`)
|
|
413
|
+
}
|
|
414
|
+
return user
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
console.error('[graphql] Error fetching user:', error)
|
|
418
|
+
throw error
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Performance
|
|
426
|
+
|
|
427
|
+
### Bundle Size
|
|
428
|
+
The module is optimized for minimal bundle size:
|
|
429
|
+
- Development dependencies are excluded from production builds
|
|
430
|
+
- Uses Function constructor for dynamic imports to prevent bundling
|
|
431
|
+
- Efficient chunk organization
|
|
432
|
+
|
|
433
|
+
### Caching
|
|
434
|
+
- Built-in cache headers for Apollo Sandbox (1 week)
|
|
435
|
+
- Configurable cache settings
|
|
436
|
+
- Lazy schema initialization
|
|
437
|
+
|
|
438
|
+
## Community & Contributing
|
|
439
|
+
|
|
440
|
+
> [!TIP]
|
|
441
|
+
> **Want to contribute?** We believe you can play a role in the growth of this project!
|
|
442
|
+
|
|
443
|
+
### 🎯 How You Can Contribute
|
|
444
|
+
|
|
445
|
+
- **💡 Share your ideas**: Use [GitHub Issues](https://github.com/productdevbook/nitro-graphql/issues) for new feature suggestions
|
|
446
|
+
- **🐛 Report bugs**: Report issues you encounter in detail
|
|
447
|
+
- **📖 Improve documentation**: Enhance README, examples, and guides
|
|
448
|
+
- **🔧 Code contributions**: Develop bug fixes and new features
|
|
449
|
+
- **🌟 Support the project**: Support the project by giving it a star
|
|
450
|
+
|
|
451
|
+
### 💬 Discussion and Support
|
|
452
|
+
|
|
453
|
+
- **GitHub Issues**: Feature requests and bug reports
|
|
454
|
+
- **GitHub Discussions**: General discussions and questions
|
|
455
|
+
- **Pull Requests**: Code contributions
|
|
456
|
+
|
|
457
|
+
### 🚀 Contribution Process
|
|
458
|
+
|
|
459
|
+
1. **Open an issue**: Let's discuss what you want to do first
|
|
460
|
+
2. **Fork & Branch**: Fork the project and create a feature branch
|
|
461
|
+
3. **Write code**: Develop according to existing code standards
|
|
462
|
+
4. **Test**: Test your changes
|
|
463
|
+
5. **Send PR**: Create a pull request with detailed description
|
|
464
|
+
|
|
465
|
+
> [!IMPORTANT]
|
|
466
|
+
> Please don't forget to read the [Contribution Guidelines](CONTRIBUTING.md) document before contributing.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
### 🌟 Thank You
|
|
471
|
+
|
|
472
|
+
Thank you for using and developing this project. Every contribution makes the GraphQL ecosystem stronger!
|
|
473
|
+
|
|
474
|
+
## License
|
|
475
|
+
|
|
476
|
+
MIT
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { consola } from "consola";
|
|
2
|
+
import { codegen } from "@graphql-codegen/core";
|
|
3
|
+
import { plugin } from "@graphql-codegen/typescript";
|
|
4
|
+
import { printSchemaWithDirectives } from "@graphql-tools/utils";
|
|
5
|
+
import { defu } from "defu";
|
|
6
|
+
import { parse } from "graphql";
|
|
7
|
+
import { plugin as plugin$1 } from "@graphql-codegen/typescript-generic-sdk";
|
|
8
|
+
import { plugin as plugin$2 } from "@graphql-codegen/typescript-operations";
|
|
9
|
+
import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
|
|
10
|
+
import { loadDocuments } from "@graphql-tools/load";
|
|
11
|
+
|
|
12
|
+
//#region src/client-codegen.ts
|
|
13
|
+
function pluginContent(_schema, _documents, _config, _info) {
|
|
14
|
+
return {
|
|
15
|
+
prepend: [
|
|
16
|
+
"// THIS FILE IS GENERATED, DO NOT EDIT!",
|
|
17
|
+
"/* eslint-disable eslint-comments/no-unlimited-disable */",
|
|
18
|
+
"/* tslint:disable */",
|
|
19
|
+
"/* eslint-disable */",
|
|
20
|
+
"/* prettier-ignore */"
|
|
21
|
+
],
|
|
22
|
+
content: ""
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function loadGraphQLDocuments(patterns) {
|
|
26
|
+
try {
|
|
27
|
+
const result = await loadDocuments(patterns, { loaders: [new GraphQLFileLoader()] });
|
|
28
|
+
return result;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
if ((e.message || "").includes("Unable to find any GraphQL type definitions for the following pointers:")) return [];
|
|
31
|
+
else throw e;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function generateClientTypes(schema, patterns, config = {}, outputPath) {
|
|
35
|
+
const docs = await loadGraphQLDocuments(patterns);
|
|
36
|
+
if (docs.length === 0) {
|
|
37
|
+
consola.info("[graphql] No client GraphQL files found. Skipping client type generation.");
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
consola.info(`[graphql] Found ${docs.length} client GraphQL documents`);
|
|
41
|
+
const defaultConfig = {
|
|
42
|
+
documentMode: "string",
|
|
43
|
+
emitLegacyCommonJSImports: false,
|
|
44
|
+
useTypeImports: true,
|
|
45
|
+
enumsAsTypes: true,
|
|
46
|
+
strictScalars: true,
|
|
47
|
+
maybeValue: "T | null | undefined",
|
|
48
|
+
inputMaybeValue: "T | undefined",
|
|
49
|
+
scalars: {
|
|
50
|
+
DateTime: "string",
|
|
51
|
+
JSON: "any",
|
|
52
|
+
UUID: "string",
|
|
53
|
+
NonEmptyString: "string",
|
|
54
|
+
Currency: "string"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const mergedConfig = defu(config, defaultConfig);
|
|
58
|
+
try {
|
|
59
|
+
const output = await codegen({
|
|
60
|
+
filename: outputPath || "client-types.generated.ts",
|
|
61
|
+
schema: parse(printSchemaWithDirectives(schema)),
|
|
62
|
+
documents: [...docs],
|
|
63
|
+
config: mergedConfig,
|
|
64
|
+
plugins: [
|
|
65
|
+
{ pluginContent: {} },
|
|
66
|
+
{ typescript: {} },
|
|
67
|
+
{ typescriptOperations: {} },
|
|
68
|
+
{ typescriptGenericSdk: { rawRequest: false } }
|
|
69
|
+
],
|
|
70
|
+
pluginMap: {
|
|
71
|
+
pluginContent: { plugin: pluginContent },
|
|
72
|
+
typescript: { plugin },
|
|
73
|
+
typescriptOperations: { plugin: plugin$2 },
|
|
74
|
+
typescriptGenericSdk: { plugin: plugin$1 }
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return output;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
consola.warn("[graphql] Client type generation failed:", error);
|
|
80
|
+
return "";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { generateClientTypes };
|
|
86
|
+
//# sourceMappingURL=client-codegen-DM2n5Gt2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-codegen-DM2n5Gt2.js","names":["_schema: any","_documents: any","_config: any","_info: any","patterns: string | string[]","e: any","schema: GraphQLSchema","config: CodegenClientConfig","outputPath?: string","defaultConfig: CodegenClientConfig","typescriptPlugin","typescriptOperations","typescriptGenericSdk"],"sources":["../src/client-codegen.ts"],"sourcesContent":["import type { GraphQLSchema } from 'graphql'\nimport type { CodegenClientConfig } from './types'\nimport { codegen } from '@graphql-codegen/core'\nimport { plugin as typescriptPlugin } from '@graphql-codegen/typescript'\nimport { plugin as typescriptGenericSdk } from '@graphql-codegen/typescript-generic-sdk'\nimport { plugin as typescriptOperations } from '@graphql-codegen/typescript-operations'\nimport { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'\nimport { loadDocuments } from '@graphql-tools/load'\nimport { printSchemaWithDirectives } from '@graphql-tools/utils'\nimport { consola } from 'consola'\nimport { defu } from 'defu'\nimport { parse } from 'graphql'\n\nfunction pluginContent(_schema: any, _documents: any, _config: any, _info: any) {\n return {\n prepend: [\n '// THIS FILE IS GENERATED, DO NOT EDIT!',\n '/* eslint-disable eslint-comments/no-unlimited-disable */',\n '/* tslint:disable */',\n '/* eslint-disable */',\n '/* prettier-ignore */',\n ],\n content: '',\n }\n}\n\nasync function loadGraphQLDocuments(patterns: string | string[]) {\n try {\n const result = await loadDocuments(patterns, {\n loaders: [new GraphQLFileLoader()],\n })\n return result\n }\n catch (e: any) {\n if (\n (e.message || '').includes(\n 'Unable to find any GraphQL type definitions for the following pointers:',\n )\n ) {\n // No GraphQL files found - this is normal\n return []\n }\n else {\n // Re-throw other errors\n throw e\n }\n }\n}\n\nexport async function generateClientTypes(\n schema: GraphQLSchema,\n patterns: string | string[],\n config: CodegenClientConfig = {},\n outputPath?: string,\n) {\n const docs = await loadGraphQLDocuments(patterns)\n\n if (docs.length === 0) {\n consola.info('[graphql] No client GraphQL files found. Skipping client type generation.')\n return ''\n }\n\n consola.info(`[graphql] Found ${docs.length} client GraphQL documents`)\n\n const defaultConfig: CodegenClientConfig = {\n documentMode: 'string',\n emitLegacyCommonJSImports: false,\n useTypeImports: true,\n enumsAsTypes: true,\n strictScalars: true,\n maybeValue: 'T | null | undefined',\n inputMaybeValue: 'T | undefined',\n scalars: {\n DateTime: 'string',\n JSON: 'any',\n UUID: 'string',\n NonEmptyString: 'string',\n Currency: 'string',\n },\n }\n\n const mergedConfig = defu(config, defaultConfig)\n\n try {\n const output = await codegen({\n filename: outputPath || 'client-types.generated.ts',\n schema: parse(printSchemaWithDirectives(schema)),\n documents: [...docs],\n config: mergedConfig,\n plugins: [\n { pluginContent: {} },\n { typescript: {} },\n { typescriptOperations: {} },\n { typescriptGenericSdk: { rawRequest: false } },\n ],\n pluginMap: {\n pluginContent: { plugin: pluginContent },\n typescript: { plugin: typescriptPlugin },\n typescriptOperations: { plugin: typescriptOperations },\n typescriptGenericSdk: { plugin: typescriptGenericSdk },\n },\n })\n\n return output\n }\n catch (error) {\n consola.warn('[graphql] Client type generation failed:', error)\n return ''\n }\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAS,cAAcA,SAAcC,YAAiBC,SAAcC,OAAY;AAC9E,QAAO;EACL,SAAS;GACP;GACA;GACA;GACA;GACA;EACD;EACD,SAAS;CACV;AACF;AAED,eAAe,qBAAqBC,UAA6B;AAC/D,KAAI;EACF,MAAM,SAAS,MAAM,cAAc,UAAU,EAC3C,SAAS,CAAC,IAAI,mBAAoB,EACnC,EAAC;AACF,SAAO;CACR,SACMC,GAAQ;AACb,MACE,CAAC,EAAE,WAAW,IAAI,SAChB,0EACD,CAGD,QAAO,CAAE;MAIT,OAAM;CAET;AACF;AAED,eAAsB,oBACpBC,QACAF,UACAG,SAA8B,CAAE,GAChCC,YACA;CACA,MAAM,OAAO,MAAM,qBAAqB,SAAS;AAEjD,KAAI,KAAK,WAAW,GAAG;AACrB,UAAQ,KAAK,4EAA4E;AACzF,SAAO;CACR;AAED,SAAQ,KAAK,CAAC,gBAAgB,EAAE,KAAK,OAAO,yBAAyB,CAAC,CAAC;CAEvE,MAAMC,gBAAqC;EACzC,cAAc;EACd,2BAA2B;EAC3B,gBAAgB;EAChB,cAAc;EACd,eAAe;EACf,YAAY;EACZ,iBAAiB;EACjB,SAAS;GACP,UAAU;GACV,MAAM;GACN,MAAM;GACN,gBAAgB;GAChB,UAAU;EACX;CACF;CAED,MAAM,eAAe,KAAK,QAAQ,cAAc;AAEhD,KAAI;EACF,MAAM,SAAS,MAAM,QAAQ;GAC3B,UAAU,cAAc;GACxB,QAAQ,MAAM,0BAA0B,OAAO,CAAC;GAChD,WAAW,CAAC,GAAG,IAAK;GACpB,QAAQ;GACR,SAAS;IACP,EAAE,eAAe,CAAE,EAAE;IACrB,EAAE,YAAY,CAAE,EAAE;IAClB,EAAE,sBAAsB,CAAE,EAAE;IAC5B,EAAE,sBAAsB,EAAE,YAAY,MAAO,EAAE;GAChD;GACD,WAAW;IACT,eAAe,EAAE,QAAQ,cAAe;IACxC,YAAY,EAAUC,OAAkB;IACxC,sBAAsB,EAAE,QAAQC,SAAsB;IACtD,sBAAsB,EAAE,QAAQC,SAAsB;GACvD;EACF,EAAC;AAEF,SAAO;CACR,SACM,OAAO;AACZ,UAAQ,KAAK,4CAA4C,MAAM;AAC/D,SAAO;CACR;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NitroGraphQLOptions } from "./types-D_NqyCcy.js";
|
|
2
|
+
import { Nitro } from "nitropack/types";
|
|
3
|
+
|
|
4
|
+
//#region src/client-watcher.d.ts
|
|
5
|
+
declare function setupClientWatcher(nitro: Nitro, options: NitroGraphQLOptions): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=client-watcher.d.ts.map
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
export { setupClientWatcher };
|
|
10
|
+
//# sourceMappingURL=client-watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-watcher.d.ts","names":[],"sources":["../src/client-watcher.ts"],"sourcesContent":[],"mappings":";;;;iBAmEsB,kBAAA,QAA0B,gBAAgB,sBAAmB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { scanGraphQLFiles } from "./scanner-BdcKEPQk.js";
|
|
2
|
+
import { debounce } from "./utils-87_22aIA.js";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { mergeTypeDefs } from "@graphql-tools/merge";
|
|
5
|
+
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
6
|
+
import { consola } from "consola";
|
|
7
|
+
import { join } from "pathe";
|
|
8
|
+
|
|
9
|
+
//#region src/client-watcher.ts
|
|
10
|
+
const logger = consola.withTag("graphql");
|
|
11
|
+
async function regenerateClientTypes(nitro, options) {
|
|
12
|
+
try {
|
|
13
|
+
if (!options.client?.enabled) return;
|
|
14
|
+
const scanResult = await scanGraphQLFiles(nitro);
|
|
15
|
+
if (scanResult.typeDefs.length === 0) {
|
|
16
|
+
logger.warn("⚠️ No server schema found for client type generation");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const mergedTypeDefs = mergeTypeDefs(scanResult.typeDefs);
|
|
20
|
+
const schema = makeExecutableSchema({
|
|
21
|
+
typeDefs: mergedTypeDefs,
|
|
22
|
+
resolvers: {}
|
|
23
|
+
});
|
|
24
|
+
const clientPatterns = options.client.watchPatterns || [
|
|
25
|
+
join(nitro.options.srcDir, "**/*.graphql"),
|
|
26
|
+
join(nitro.options.srcDir, "**/*.gql"),
|
|
27
|
+
`!${join(nitro.options.srcDir, "graphql/**/*")}`
|
|
28
|
+
];
|
|
29
|
+
const { generateClientTypes } = await import("./client-codegen-DM2n5Gt2.js");
|
|
30
|
+
const generatedTypes = await generateClientTypes(schema, clientPatterns, options.client.config, options.client.outputPath);
|
|
31
|
+
if (generatedTypes) {
|
|
32
|
+
const outputPath = options.client.outputPath || join(nitro.options.buildDir, "types", "graphql-client.generated.ts");
|
|
33
|
+
const typesDir = join(nitro.options.buildDir, "types");
|
|
34
|
+
await mkdir(typesDir, { recursive: true });
|
|
35
|
+
await writeFile(outputPath, generatedTypes);
|
|
36
|
+
logger.success("✨ Client types updated");
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
40
|
+
logger.error("❌ Client type generation failed:", errorMessage);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function setupClientWatcher(nitro, options) {
|
|
44
|
+
if (!options.client?.enabled) {
|
|
45
|
+
logger.info("🚫 Client type generation disabled");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const clientPatterns = options.client.watchPatterns || [join(nitro.options.srcDir, "**/*.graphql"), join(nitro.options.srcDir, "**/*.gql")];
|
|
49
|
+
const generateClientTypesDebounced = debounce(async () => {
|
|
50
|
+
await regenerateClientTypes(nitro, options);
|
|
51
|
+
}, 300);
|
|
52
|
+
const { watch } = await import("chokidar");
|
|
53
|
+
const { globby } = await import("globby");
|
|
54
|
+
const existingClientFiles = await globby(clientPatterns, {
|
|
55
|
+
absolute: true,
|
|
56
|
+
ignore: [join(nitro.options.srcDir, "graphql/**/*")]
|
|
57
|
+
});
|
|
58
|
+
const watchPatterns = existingClientFiles.length > 0 ? existingClientFiles : clientPatterns;
|
|
59
|
+
const watcher = watch(watchPatterns, {
|
|
60
|
+
persistent: true,
|
|
61
|
+
ignoreInitial: true,
|
|
62
|
+
ignored: /(^|[/\\])\../,
|
|
63
|
+
followSymlinks: false,
|
|
64
|
+
depth: 10,
|
|
65
|
+
usePolling: true,
|
|
66
|
+
interval: 1e3,
|
|
67
|
+
binaryInterval: 1e3
|
|
68
|
+
});
|
|
69
|
+
watcher.on("change", (_path) => {
|
|
70
|
+
generateClientTypesDebounced();
|
|
71
|
+
});
|
|
72
|
+
watcher.on("add", (_path) => {
|
|
73
|
+
generateClientTypesDebounced();
|
|
74
|
+
});
|
|
75
|
+
watcher.on("unlink", (_path) => {
|
|
76
|
+
generateClientTypesDebounced();
|
|
77
|
+
});
|
|
78
|
+
watcher.on("error", (error) => {
|
|
79
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
80
|
+
logger.error("❌ Client watcher error:", errorMessage);
|
|
81
|
+
});
|
|
82
|
+
nitro.hooks.hook("close", () => {
|
|
83
|
+
logger.info("🔒 Closing client watcher");
|
|
84
|
+
watcher.close();
|
|
85
|
+
});
|
|
86
|
+
await generateClientTypesDebounced();
|
|
87
|
+
logger.success("✅ Client watcher ready");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
export { setupClientWatcher };
|
|
92
|
+
//# sourceMappingURL=client-watcher.js.map
|