nitro-graphql 1.1.2 → 1.2.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 +416 -1
- package/dist/ecosystem/nuxt.d.ts +2 -2
- package/dist/ecosystem/nuxt.js +8 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +27 -103
- package/dist/rollup.js +1 -1
- package/dist/routes/graphql-yoga.d.ts +2 -2
- package/dist/types/index.d.ts +30 -1
- package/dist/utils/client-codegen.d.ts +18 -3
- package/dist/utils/client-codegen.js +114 -8
- package/dist/utils/directive-parser.d.ts +80 -0
- package/dist/utils/directive-parser.js +235 -0
- package/dist/utils/index.d.ts +10 -1
- package/dist/utils/index.js +45 -3
- package/dist/utils/type-generation.js +145 -21
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -30,8 +30,23 @@
|
|
|
30
30
|
- 🔄 **Hot Reload**: Development mode with automatic schema and resolver updates
|
|
31
31
|
- 📦 **Optimized Bundling**: Smart chunking and dynamic imports for production
|
|
32
32
|
- 🌐 **Nuxt Integration**: First-class Nuxt.js support with dedicated module
|
|
33
|
+
- 🎭 **Custom Directives**: Create reusable GraphQL directives with automatic schema generation
|
|
34
|
+
- 🔗 **Multi-Service Support**: Connect to multiple external GraphQL APIs alongside your main server
|
|
33
35
|
|
|
34
|
-
##
|
|
36
|
+
## 🎯 Used Projects
|
|
37
|
+
|
|
38
|
+
Projects using Nitro GraphQL in production:
|
|
39
|
+
|
|
40
|
+
- [**Nitroping**](https://github.com/productdevbook/nitroping) - Open-source, self-hosted push notification service
|
|
41
|
+
|
|
42
|
+
## 🎥 Video Tutorials
|
|
43
|
+
|
|
44
|
+
Learn how to use Nitro GraphQL with these video tutorials:
|
|
45
|
+
|
|
46
|
+
- [**Nuxt 4 Usage**](https://x.com/productdevbook/status/1947314569531076633) - How to integrate Nitro GraphQL with Nuxt 4
|
|
47
|
+
- [**Nitro Usage**](https://x.com/productdevbook/status/1945759751393976348) - How to use Nitro GraphQL with standalone Nitro
|
|
48
|
+
|
|
49
|
+
## 🎯 Quick Start
|
|
35
50
|
|
|
36
51
|
### Step 1: Installation
|
|
37
52
|
|
|
@@ -181,6 +196,10 @@ server/
|
|
|
181
196
|
├── graphql/
|
|
182
197
|
│ ├── schema.graphql # Main schema with scalars and base types
|
|
183
198
|
│ ├── hello.resolver.ts # Global resolvers (use named exports)
|
|
199
|
+
│ ├── directives/ # Custom GraphQL directives
|
|
200
|
+
│ │ ├── auth.directive.ts # Authentication directive
|
|
201
|
+
│ │ ├── cache.directive.ts # Caching directive
|
|
202
|
+
│ │ └── validate.directive.ts # Validation directive
|
|
184
203
|
│ ├── users/
|
|
185
204
|
│ │ ├── user.graphql # User schema definitions
|
|
186
205
|
│ │ ├── user-queries.resolver.ts # User query resolvers (use named exports)
|
|
@@ -615,6 +634,90 @@ export const postTypes = defineType({
|
|
|
615
634
|
|
|
616
635
|
</details>
|
|
617
636
|
|
|
637
|
+
<details>
|
|
638
|
+
<summary><strong>defineDirective</strong> - Create custom GraphQL directives</summary>
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
import { defineDirective } from 'nitro-graphql/utils/define'
|
|
642
|
+
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'
|
|
643
|
+
import { defaultFieldResolver, GraphQLError } from 'graphql'
|
|
644
|
+
|
|
645
|
+
export const authDirective = defineDirective({
|
|
646
|
+
name: 'auth',
|
|
647
|
+
locations: ['FIELD_DEFINITION', 'OBJECT'],
|
|
648
|
+
args: {
|
|
649
|
+
requires: {
|
|
650
|
+
type: 'String',
|
|
651
|
+
defaultValue: 'USER',
|
|
652
|
+
description: 'Required role to access this field',
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
description: 'Directive to check authentication and authorization',
|
|
656
|
+
transformer: (schema) => {
|
|
657
|
+
return mapSchema(schema, {
|
|
658
|
+
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
|
|
659
|
+
const authDirectiveConfig = getDirective(schema, fieldConfig, 'auth')?.[0]
|
|
660
|
+
|
|
661
|
+
if (authDirectiveConfig) {
|
|
662
|
+
const { resolve = defaultFieldResolver } = fieldConfig
|
|
663
|
+
|
|
664
|
+
fieldConfig.resolve = async function (source, args, context, info) {
|
|
665
|
+
if (!context.user) {
|
|
666
|
+
throw new GraphQLError('You must be logged in')
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (context.user.role !== authDirectiveConfig.requires) {
|
|
670
|
+
throw new GraphQLError('Insufficient permissions')
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return resolve(source, args, context, info)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return fieldConfig
|
|
678
|
+
},
|
|
679
|
+
})
|
|
680
|
+
},
|
|
681
|
+
})
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**Usage in Schema:**
|
|
685
|
+
```graphql
|
|
686
|
+
type User {
|
|
687
|
+
id: ID!
|
|
688
|
+
name: String!
|
|
689
|
+
email: String! @auth(requires: "ADMIN")
|
|
690
|
+
secretData: String @auth(requires: "SUPER_ADMIN")
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
type Query {
|
|
694
|
+
users: [User!]! @auth
|
|
695
|
+
adminStats: AdminStats @auth(requires: "ADMIN")
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**Available Argument Types:**
|
|
700
|
+
- Basic scalars: `String`, `Int`, `Float`, `Boolean`, `ID`, `JSON`, `DateTime`
|
|
701
|
+
- Non-nullable: `String!`, `Int!`, `Float!`, `Boolean!`, `ID!`, `JSON!`, `DateTime!`
|
|
702
|
+
- Arrays: `[String]`, `[String!]`, `[String]!`, `[String!]!` (and all combinations for other types)
|
|
703
|
+
- Custom types: Any string for your custom GraphQL types
|
|
704
|
+
|
|
705
|
+
**Helper Function:**
|
|
706
|
+
```ts
|
|
707
|
+
export const validateDirective = defineDirective({
|
|
708
|
+
name: 'validate',
|
|
709
|
+
locations: ['FIELD_DEFINITION', 'ARGUMENT_DEFINITION'],
|
|
710
|
+
args: {
|
|
711
|
+
minLength: arg('Int', { description: 'Minimum length' }),
|
|
712
|
+
maxLength: arg('Int', { description: 'Maximum length' }),
|
|
713
|
+
pattern: arg('String', { description: 'Regex pattern' }),
|
|
714
|
+
},
|
|
715
|
+
// ... transformer implementation
|
|
716
|
+
})
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
</details>
|
|
720
|
+
|
|
618
721
|
<details>
|
|
619
722
|
<summary><strong>defineSchema</strong> - Define custom schema with validation</summary>
|
|
620
723
|
|
|
@@ -741,6 +844,75 @@ export default defineNitroConfig({
|
|
|
741
844
|
|
|
742
845
|
## 🔥 Advanced Features
|
|
743
846
|
|
|
847
|
+
<details>
|
|
848
|
+
<summary><strong>Custom Directives</strong></summary>
|
|
849
|
+
|
|
850
|
+
Create reusable GraphQL directives with automatic schema generation:
|
|
851
|
+
|
|
852
|
+
```ts
|
|
853
|
+
// server/graphql/directives/auth.directive.ts
|
|
854
|
+
import { defineDirective } from 'nitro-graphql/utils/define'
|
|
855
|
+
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'
|
|
856
|
+
|
|
857
|
+
export const authDirective = defineDirective({
|
|
858
|
+
name: 'auth',
|
|
859
|
+
locations: ['FIELD_DEFINITION', 'OBJECT'],
|
|
860
|
+
args: {
|
|
861
|
+
requires: {
|
|
862
|
+
type: 'String',
|
|
863
|
+
defaultValue: 'USER',
|
|
864
|
+
description: 'Required role to access this field',
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
description: 'Authentication and authorization directive',
|
|
868
|
+
transformer: (schema) => {
|
|
869
|
+
return mapSchema(schema, {
|
|
870
|
+
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
|
|
871
|
+
const authConfig = getDirective(schema, fieldConfig, 'auth')?.[0]
|
|
872
|
+
if (authConfig) {
|
|
873
|
+
// Transform field resolvers to check authentication
|
|
874
|
+
const { resolve = defaultFieldResolver } = fieldConfig
|
|
875
|
+
fieldConfig.resolve = async (source, args, context, info) => {
|
|
876
|
+
if (!context.user || context.user.role !== authConfig.requires) {
|
|
877
|
+
throw new GraphQLError('Access denied')
|
|
878
|
+
}
|
|
879
|
+
return resolve(source, args, context, info)
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return fieldConfig
|
|
883
|
+
},
|
|
884
|
+
})
|
|
885
|
+
},
|
|
886
|
+
})
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
**Common Directive Examples:**
|
|
890
|
+
- `@auth(requires: "ADMIN")` - Role-based authentication
|
|
891
|
+
- `@cache(ttl: 300, scope: "PUBLIC")` - Field-level caching
|
|
892
|
+
- `@rateLimit(limit: 10, window: 60)` - Rate limiting
|
|
893
|
+
- `@validate(minLength: 5, maxLength: 100)` - Input validation
|
|
894
|
+
- `@transform(upper: true, trim: true)` - Data transformation
|
|
895
|
+
- `@permission(roles: ["ADMIN", "MODERATOR"])` - Multi-role permissions
|
|
896
|
+
|
|
897
|
+
**Usage in Schema:**
|
|
898
|
+
```graphql
|
|
899
|
+
type User {
|
|
900
|
+
id: ID!
|
|
901
|
+
name: String!
|
|
902
|
+
email: String! @auth(requires: "ADMIN")
|
|
903
|
+
posts: [Post!]! @cache(ttl: 300)
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
type Query {
|
|
907
|
+
users: [User!]! @rateLimit(limit: 100, window: 3600)
|
|
908
|
+
sensitiveData: String @auth(requires: "SUPER_ADMIN")
|
|
909
|
+
}
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
The module automatically generates the directive schema definitions and integrates them with both GraphQL Yoga and Apollo Server.
|
|
913
|
+
|
|
914
|
+
</details>
|
|
915
|
+
|
|
744
916
|
<details>
|
|
745
917
|
<summary><strong>Custom Scalars</strong></summary>
|
|
746
918
|
|
|
@@ -1102,6 +1274,249 @@ Help us improve nitro-graphql! Pick any item and contribute:
|
|
|
1102
1274
|
> [!NOTE]
|
|
1103
1275
|
> Have other ideas? Open an issue to discuss!
|
|
1104
1276
|
|
|
1277
|
+
## 🔗 Multi-Service GraphQL Support
|
|
1278
|
+
|
|
1279
|
+
Connect to multiple external GraphQL APIs alongside your main GraphQL server. Perfect for integrating with services like GitHub API, Shopify API, or any GraphQL endpoint.
|
|
1280
|
+
|
|
1281
|
+
### Configuration
|
|
1282
|
+
|
|
1283
|
+
```typescript
|
|
1284
|
+
// nuxt.config.ts (for Nuxt projects)
|
|
1285
|
+
export default defineNuxtConfig({
|
|
1286
|
+
nitro: {
|
|
1287
|
+
graphql: {
|
|
1288
|
+
framework: 'graphql-yoga',
|
|
1289
|
+
externalServices: [
|
|
1290
|
+
{
|
|
1291
|
+
name: 'countries',
|
|
1292
|
+
schema: 'https://countries.trevorblades.com',
|
|
1293
|
+
endpoint: 'https://countries.trevorblades.com',
|
|
1294
|
+
documents: ['app/graphql/external/countries/**/*.graphql'],
|
|
1295
|
+
headers: {
|
|
1296
|
+
// Optional: Add custom headers
|
|
1297
|
+
'Authorization': 'Bearer your-token'
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
name: 'github',
|
|
1302
|
+
schema: 'https://api.github.com/graphql',
|
|
1303
|
+
endpoint: 'https://api.github.com/graphql',
|
|
1304
|
+
documents: ['app/graphql/external/github/**/*.graphql'],
|
|
1305
|
+
headers: () => ({
|
|
1306
|
+
// Dynamic headers with function
|
|
1307
|
+
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`
|
|
1308
|
+
})
|
|
1309
|
+
}
|
|
1310
|
+
]
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
})
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
### Schema Download & Caching (Optional)
|
|
1317
|
+
|
|
1318
|
+
For better performance and offline development, you can download and cache external schemas locally:
|
|
1319
|
+
|
|
1320
|
+
```typescript
|
|
1321
|
+
// nuxt.config.ts
|
|
1322
|
+
export default defineNuxtConfig({
|
|
1323
|
+
nitro: {
|
|
1324
|
+
graphql: {
|
|
1325
|
+
framework: 'graphql-yoga',
|
|
1326
|
+
externalServices: [
|
|
1327
|
+
{
|
|
1328
|
+
name: 'github',
|
|
1329
|
+
schema: 'https://docs.github.com/public/schema.docs.graphql',
|
|
1330
|
+
endpoint: 'https://api.github.com/graphql',
|
|
1331
|
+
downloadSchema: 'once', // Download mode (see options below)
|
|
1332
|
+
downloadPath: './schemas/github.graphql', // Optional: custom download path
|
|
1333
|
+
headers: () => ({
|
|
1334
|
+
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`
|
|
1335
|
+
})
|
|
1336
|
+
}
|
|
1337
|
+
]
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
})
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
**Download Modes:**
|
|
1344
|
+
|
|
1345
|
+
| Mode | Behavior | Use Case |
|
|
1346
|
+
|------|----------|----------|
|
|
1347
|
+
| `true` or `'once'` | Download only if file doesn't exist | **Offline-friendly development** |
|
|
1348
|
+
| `'always'` | Check for updates on every build | **Always stay up-to-date** |
|
|
1349
|
+
| `'manual'` | Never download automatically | **Full manual control** |
|
|
1350
|
+
| `false` | Disable schema downloading | **Always use remote** |
|
|
1351
|
+
|
|
1352
|
+
**Benefits:**
|
|
1353
|
+
- **Offline Development**: Work without internet connection after initial download
|
|
1354
|
+
- **Faster Builds**: No remote fetching on each build when using 'once' mode
|
|
1355
|
+
- **Version Control**: Commit downloaded schemas to track API changes
|
|
1356
|
+
- **Network Reliability**: Fallback to cached schema if remote is unavailable
|
|
1357
|
+
|
|
1358
|
+
**How it works:**
|
|
1359
|
+
- **'once' mode (recommended)**: Downloads schema only if file doesn't exist, then uses cached version
|
|
1360
|
+
- **'always' mode**: Checks for schema changes on every build using hash comparison
|
|
1361
|
+
- **'manual' mode**: User manages schema files manually, no automatic downloading
|
|
1362
|
+
|
|
1363
|
+
**File locations:**
|
|
1364
|
+
- Default: `.nitro/graphql/schemas/[serviceName].graphql`
|
|
1365
|
+
- Custom: Use `downloadPath` option to specify your preferred location
|
|
1366
|
+
|
|
1367
|
+
### Usage
|
|
1368
|
+
|
|
1369
|
+
#### 1. Create External Service Queries
|
|
1370
|
+
|
|
1371
|
+
```graphql
|
|
1372
|
+
<!-- app/graphql/external/countries/countries.graphql -->
|
|
1373
|
+
query GetCountries {
|
|
1374
|
+
countries {
|
|
1375
|
+
code
|
|
1376
|
+
name
|
|
1377
|
+
emoji
|
|
1378
|
+
continent {
|
|
1379
|
+
name
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
query GetCountry($code: ID!) {
|
|
1385
|
+
country(code: $code) {
|
|
1386
|
+
code
|
|
1387
|
+
name
|
|
1388
|
+
capital
|
|
1389
|
+
currency
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
#### 2. Use Generated SDKs
|
|
1395
|
+
|
|
1396
|
+
```typescript
|
|
1397
|
+
// Import from centralized index
|
|
1398
|
+
import { $sdk, $countriesSdk, $githubSdk } from '~/app/graphql'
|
|
1399
|
+
|
|
1400
|
+
// Or import directly from service folders
|
|
1401
|
+
import { $countriesSdk } from '~/app/graphql/countries/ofetch'
|
|
1402
|
+
|
|
1403
|
+
// Use in components
|
|
1404
|
+
const countries = await $countriesSdk.GetCountries()
|
|
1405
|
+
const country = await $countriesSdk.GetCountry({ code: 'US' })
|
|
1406
|
+
|
|
1407
|
+
// Your main service still works
|
|
1408
|
+
const users = await $sdk.GetUsers()
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
#### 3. Folder Structure
|
|
1412
|
+
|
|
1413
|
+
After configuration, your project structure becomes:
|
|
1414
|
+
|
|
1415
|
+
```
|
|
1416
|
+
app/graphql/
|
|
1417
|
+
├── index.ts # Centralized exports (auto-generated)
|
|
1418
|
+
├── default/ # Your main GraphQL service
|
|
1419
|
+
│ ├── ofetch.ts # Main service client
|
|
1420
|
+
│ └── sdk.ts # Main service SDK
|
|
1421
|
+
├── countries/ # External countries service
|
|
1422
|
+
│ ├── ofetch.ts # Countries service client
|
|
1423
|
+
│ └── sdk.ts # Countries service SDK
|
|
1424
|
+
├── github/ # External GitHub service
|
|
1425
|
+
│ ├── ofetch.ts # GitHub service client
|
|
1426
|
+
│ └── sdk.ts # GitHub service SDK
|
|
1427
|
+
└── external/ # Your external service queries
|
|
1428
|
+
├── countries/
|
|
1429
|
+
│ └── countries.graphql
|
|
1430
|
+
└── github/
|
|
1431
|
+
└── repositories.graphql
|
|
1432
|
+
```
|
|
1433
|
+
|
|
1434
|
+
#### 4. TypeScript Support
|
|
1435
|
+
|
|
1436
|
+
Each service gets its own type definitions:
|
|
1437
|
+
|
|
1438
|
+
```typescript
|
|
1439
|
+
// Types are automatically generated and available
|
|
1440
|
+
import type { GetCountriesQuery } from '#graphql/client/countries'
|
|
1441
|
+
import type { GetUsersQuery } from '#graphql/client'
|
|
1442
|
+
|
|
1443
|
+
const handleCountries = (countries: GetCountriesQuery) => {
|
|
1444
|
+
// Fully typed countries data
|
|
1445
|
+
}
|
|
1446
|
+
```
|
|
1447
|
+
|
|
1448
|
+
### Service Configuration Options
|
|
1449
|
+
|
|
1450
|
+
| Option | Type | Required | Description |
|
|
1451
|
+
|--------|------|----------|-------------|
|
|
1452
|
+
| `name` | `string` | ✅ | Unique service name (used for folder/file names) |
|
|
1453
|
+
| `schema` | `string` \| `string[]` | ✅ | GraphQL schema URL or file path |
|
|
1454
|
+
| `endpoint` | `string` | ✅ | GraphQL endpoint URL for queries |
|
|
1455
|
+
| `documents` | `string[]` | ❌ | Glob patterns for GraphQL query files |
|
|
1456
|
+
| `headers` | `Record<string, string>` \| `() => Record<string, string>` | ❌ | Custom headers for schema introspection and queries |
|
|
1457
|
+
| `codegen.client` | `CodegenClientConfig` | ❌ | Custom codegen configuration for client types |
|
|
1458
|
+
| `codegen.clientSDK` | `GenericSdkConfig` | ❌ | Custom codegen configuration for SDK generation |
|
|
1459
|
+
|
|
1460
|
+
## 🛠️ GraphQL Config (Optional but Recommended)
|
|
1461
|
+
|
|
1462
|
+
To enable GraphQL language features in your IDE (autocompletion, validation, go-to definition), create a `graphql.config.ts` file in your project root:
|
|
1463
|
+
|
|
1464
|
+
### For Single Service (Main GraphQL Server Only)
|
|
1465
|
+
|
|
1466
|
+
```typescript
|
|
1467
|
+
// graphql.config.ts
|
|
1468
|
+
import type { IGraphQLConfig } from 'graphql-config'
|
|
1469
|
+
|
|
1470
|
+
export default <IGraphQLConfig> {
|
|
1471
|
+
schema: ['./.nuxt/graphql/schema.graphql'],
|
|
1472
|
+
documents: ['./app/graphql/**/*.{graphql,js,ts,jsx,tsx}'],
|
|
1473
|
+
exclude: ['./app/graphql/external/**/*'] // Exclude external service documents
|
|
1474
|
+
}
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
### For Multi-Service Setup
|
|
1478
|
+
|
|
1479
|
+
```typescript
|
|
1480
|
+
// graphql.config.ts
|
|
1481
|
+
import type { IGraphQLConfig } from 'graphql-config'
|
|
1482
|
+
|
|
1483
|
+
export default <IGraphQLConfig> {
|
|
1484
|
+
projects: {
|
|
1485
|
+
// Main GraphQL server
|
|
1486
|
+
default: {
|
|
1487
|
+
schema: ['./.nuxt/graphql/schema.graphql'],
|
|
1488
|
+
documents: ['./app/graphql/default/**/*.{graphql,js,ts,jsx,tsx}']
|
|
1489
|
+
},
|
|
1490
|
+
// External services
|
|
1491
|
+
github: {
|
|
1492
|
+
schema: [
|
|
1493
|
+
// Use downloaded schema if available, otherwise use remote
|
|
1494
|
+
'./.nuxt/graphql/schemas/github.graphql',
|
|
1495
|
+
// Fallback to remote if local doesn't exist
|
|
1496
|
+
'https://docs.github.com/public/schema.docs.graphql'
|
|
1497
|
+
],
|
|
1498
|
+
documents: ['./app/graphql/external/github/**/*.graphql']
|
|
1499
|
+
},
|
|
1500
|
+
countries: {
|
|
1501
|
+
schema: ['./.nuxt/graphql/schemas/countries.graphql'],
|
|
1502
|
+
documents: ['./app/graphql/external/countries/**/*.graphql']
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
```
|
|
1507
|
+
|
|
1508
|
+
### Schema Paths for Different Download Modes
|
|
1509
|
+
|
|
1510
|
+
- **Downloaded schemas**: `./.nuxt/graphql/schemas/[serviceName].graphql`
|
|
1511
|
+
- **Custom download path**: Use your `downloadPath` configuration
|
|
1512
|
+
- **Remote fallback**: Include remote URL as second option
|
|
1513
|
+
|
|
1514
|
+
This configuration enables:
|
|
1515
|
+
- 🎯 **Service-specific validation**: Each GraphQL service gets its own validation rules
|
|
1516
|
+
- 🚀 **IDE autocompletion**: Full IntelliSense for queries and mutations
|
|
1517
|
+
- ✅ **Real-time validation**: Catch GraphQL errors while typing
|
|
1518
|
+
- 🔍 **Go-to definition**: Navigate to type definitions across services
|
|
1519
|
+
|
|
1105
1520
|
## 🛠️ VS Code Extensions
|
|
1106
1521
|
|
|
1107
1522
|
For the best development experience with GraphQL, install these recommended VS Code extensions:
|
package/dist/ecosystem/nuxt.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as _nuxt_schema3 from "@nuxt/schema";
|
|
2
2
|
|
|
3
3
|
//#region src/ecosystem/nuxt.d.ts
|
|
4
4
|
interface ModuleOptions {}
|
|
5
|
-
declare const _default:
|
|
5
|
+
declare const _default: _nuxt_schema3.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { ModuleOptions, _default as default };
|
package/dist/ecosystem/nuxt.js
CHANGED
|
@@ -15,11 +15,19 @@ var nuxt_default = defineNuxtModule({
|
|
|
15
15
|
options.tsConfig.compilerOptions ??= {};
|
|
16
16
|
options.tsConfig.compilerOptions.paths ??= {};
|
|
17
17
|
options.tsConfig.compilerOptions.paths["#graphql/client"] = ["./types/nitro-graphql-client.d.ts"];
|
|
18
|
+
const externalServices$1 = nuxt.options.nitro?.graphql?.externalServices || [];
|
|
19
|
+
for (const service of externalServices$1) {
|
|
20
|
+
options.references.push({ path: `types/nitro-graphql-client-${service.name}.d.ts` });
|
|
21
|
+
options.tsConfig.compilerOptions.paths[`#graphql/client/${service.name}`] = [`./types/nitro-graphql-client-${service.name}.d.ts`];
|
|
22
|
+
}
|
|
18
23
|
options.tsConfig.include = options.tsConfig.include || [];
|
|
19
24
|
options.tsConfig.include.push("./types/nitro-graphql-client.d.ts");
|
|
25
|
+
for (const service of externalServices$1) options.tsConfig.include.push(`./types/nitro-graphql-client-${service.name}.d.ts`);
|
|
20
26
|
});
|
|
21
27
|
nuxt.options.alias = nuxt.options.alias || {};
|
|
22
28
|
nuxt.options.alias["#graphql/client"] = join(nuxt.options.buildDir, "types/nitro-graphql-client.d.ts");
|
|
29
|
+
const externalServices = nuxt.options.nitro?.graphql?.externalServices || [];
|
|
30
|
+
for (const service of externalServices) nuxt.options.alias[`#graphql/client/${service.name}`] = join(nuxt.options.buildDir, `types/nitro-graphql-client-${service.name}.d.ts`);
|
|
23
31
|
nuxt.hook("imports:dirs", (dirs) => {
|
|
24
32
|
dirs.push(resolve(nuxt.options.srcDir, "graphql"));
|
|
25
33
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from "./types/standard-schema.js";
|
|
2
|
-
import { CodegenClientConfig, CodegenServerConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions } from "./types/index.js";
|
|
3
|
-
import * as
|
|
2
|
+
import { CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, GenImport, GenericSdkConfig, NitroGraphQLOptions } from "./types/index.js";
|
|
3
|
+
import * as nitropack2 from "nitropack";
|
|
4
4
|
|
|
5
5
|
//#region src/index.d.ts
|
|
6
|
-
declare const _default:
|
|
6
|
+
declare const _default: nitropack2.NitroModule;
|
|
7
7
|
//#endregion
|
|
8
|
-
export { CodegenClientConfig, CodegenServerConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, StandardSchemaV1, _default as default };
|
|
8
|
+
export { CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, GenImport, GenericSdkConfig, NitroGraphQLOptions, StandardSchemaV1, _default as default };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { generateDirectiveSchemas } from "./utils/directive-parser.js";
|
|
2
|
+
import { relativeWithDot, scanDirectives, scanDocs, scanResolvers, scanSchemas, validateExternalServices } from "./utils/index.js";
|
|
2
3
|
import { clientTypeGeneration, serverTypeGeneration } from "./utils/type-generation.js";
|
|
3
4
|
import { rollupConfig } from "./rollup.js";
|
|
4
|
-
import { existsSync, mkdirSync,
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { watch } from "chokidar";
|
|
8
8
|
import consola from "consola";
|
|
@@ -15,6 +15,15 @@ var src_default = defineNitroModule({
|
|
|
15
15
|
name: "nitro-graphql",
|
|
16
16
|
async setup(nitro) {
|
|
17
17
|
if (!nitro.options.graphql?.framework) consola.warn("No GraphQL framework specified. Please set graphql.framework to \"graphql-yoga\" or \"apollo-server\".");
|
|
18
|
+
if (nitro.options.graphql?.externalServices?.length) {
|
|
19
|
+
const validationErrors = validateExternalServices(nitro.options.graphql.externalServices);
|
|
20
|
+
if (validationErrors.length > 0) {
|
|
21
|
+
consola.error("External services configuration errors:");
|
|
22
|
+
for (const error of validationErrors) consola.error(` - ${error}`);
|
|
23
|
+
throw new Error("Invalid external services configuration");
|
|
24
|
+
}
|
|
25
|
+
consola.info(`Configured ${nitro.options.graphql.externalServices.length} external GraphQL services`);
|
|
26
|
+
}
|
|
18
27
|
nitro.graphql ||= {
|
|
19
28
|
buildDir: "",
|
|
20
29
|
watchDirs: [],
|
|
@@ -60,6 +69,13 @@ var src_default = defineNitroModule({
|
|
|
60
69
|
break;
|
|
61
70
|
default:
|
|
62
71
|
}
|
|
72
|
+
if (nitro.options.graphql?.externalServices?.length) {
|
|
73
|
+
for (const service of nitro.options.graphql.externalServices) if (service.documents?.length) for (const pattern of service.documents) {
|
|
74
|
+
const baseDir = pattern.split("**")[0].replace(/\/$/, "") || ".";
|
|
75
|
+
const resolvedDir = resolve(nitro.options.rootDir, baseDir);
|
|
76
|
+
if (!watchDirs.includes(resolvedDir)) watchDirs.push(resolvedDir);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
63
79
|
const watcher = watch(watchDirs, {
|
|
64
80
|
persistent: true,
|
|
65
81
|
ignoreInitial: true,
|
|
@@ -81,56 +97,7 @@ var src_default = defineNitroModule({
|
|
|
81
97
|
nitro.scanResolvers = resolvers;
|
|
82
98
|
const directives = await scanDirectives(nitro);
|
|
83
99
|
nitro.scanDirectives = directives;
|
|
84
|
-
|
|
85
|
-
const directiveSchemas = [];
|
|
86
|
-
for (const dir of directives) for (const _imp of dir.imports) {
|
|
87
|
-
const fileContent = await readFile(dir.specifier, "utf-8");
|
|
88
|
-
const nameMatch = fileContent.match(/name:\s*['"`](\w+)['"`]/);
|
|
89
|
-
const locationsMatch = fileContent.match(/locations:\s*\[([\s\S]*?)\]/);
|
|
90
|
-
const argsMatch = fileContent.match(/args:\s*\{([\s\S]*?)\}\s*,\s*(?:description|transformer)/);
|
|
91
|
-
if (nameMatch && locationsMatch) {
|
|
92
|
-
const name = nameMatch[1];
|
|
93
|
-
const locations = locationsMatch?.[1]?.split(",").map((l) => l.trim().replace(/['"`]/g, "")).filter(Boolean).join(" | ") || "";
|
|
94
|
-
let args = "";
|
|
95
|
-
if (argsMatch && argsMatch[1] && argsMatch[1].trim()) {
|
|
96
|
-
const argDefs = [];
|
|
97
|
-
const argMatches = argsMatch[1].matchAll(/(\w+):\s*\{([^}]+)\}/g);
|
|
98
|
-
for (const argMatch of argMatches) {
|
|
99
|
-
const argName = argMatch[1];
|
|
100
|
-
const argBody = argMatch[2];
|
|
101
|
-
const typeMatch = argBody?.match(/type:\s*['"`](\[[\w!]+\]|\w+)['"`]/);
|
|
102
|
-
const type = typeMatch ? typeMatch[1] : "String";
|
|
103
|
-
const defaultMatch = argBody?.match(/defaultValue:\s*(['"`]([^'"`]+)['"`]|(\d+)|true|false)/);
|
|
104
|
-
let defaultValue = "";
|
|
105
|
-
if (defaultMatch) {
|
|
106
|
-
const value = defaultMatch[2] || defaultMatch[3] || defaultMatch[1]?.replace(/['"`]/g, "");
|
|
107
|
-
if (type === "String") defaultValue = ` = "${value}"`;
|
|
108
|
-
else if (type === "Int" || type === "Float") defaultValue = ` = ${value}`;
|
|
109
|
-
else if (type === "Boolean") defaultValue = ` = ${value}`;
|
|
110
|
-
}
|
|
111
|
-
argDefs.push(`${argName}: ${type}${defaultValue}`);
|
|
112
|
-
}
|
|
113
|
-
if (argDefs.length > 0) args = `(${argDefs.join(", ")})`;
|
|
114
|
-
}
|
|
115
|
-
directiveSchemas.push(`directive @${name}${args} on ${locations}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (directiveSchemas.length > 0) {
|
|
119
|
-
const directivesPath = resolve(nitro.graphql.serverDir, "_directives.graphql");
|
|
120
|
-
const content = `# WARNING: This file is auto-generated by nitro-graphql
|
|
121
|
-
# Do not modify this file directly. It will be overwritten.
|
|
122
|
-
# To define custom directives, create .directive.ts files using defineDirective()
|
|
123
|
-
|
|
124
|
-
${directiveSchemas.join("\n\n")}`;
|
|
125
|
-
let shouldWrite = true;
|
|
126
|
-
if (existsSync(directivesPath)) {
|
|
127
|
-
const existingContent = readFileSync(directivesPath, "utf-8");
|
|
128
|
-
shouldWrite = existingContent !== content;
|
|
129
|
-
}
|
|
130
|
-
if (shouldWrite) writeFileSync(directivesPath, content, "utf-8");
|
|
131
|
-
if (!nitro.scanSchemas.includes(directivesPath)) nitro.scanSchemas.push(directivesPath);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
100
|
+
await generateDirectiveSchemas(nitro, directives);
|
|
134
101
|
nitro.hooks.hook("dev:start", async () => {
|
|
135
102
|
const schemas$1 = await scanSchemas(nitro);
|
|
136
103
|
nitro.scanSchemas = schemas$1;
|
|
@@ -138,56 +105,7 @@ ${directiveSchemas.join("\n\n")}`;
|
|
|
138
105
|
nitro.scanResolvers = resolvers$1;
|
|
139
106
|
const directives$1 = await scanDirectives(nitro);
|
|
140
107
|
nitro.scanDirectives = directives$1;
|
|
141
|
-
|
|
142
|
-
const directiveSchemas = [];
|
|
143
|
-
for (const dir of directives$1) for (const _imp of dir.imports) {
|
|
144
|
-
const fileContent = await readFile(dir.specifier, "utf-8");
|
|
145
|
-
const nameMatch = fileContent.match(/name:\s*['"`](\w+)['"`]/);
|
|
146
|
-
const locationsMatch = fileContent.match(/locations:\s*\[([\s\S]*?)\]/);
|
|
147
|
-
const argsMatch = fileContent.match(/args:\s*\{([\s\S]*?)\}\s*,\s*(?:description|transformer)/);
|
|
148
|
-
if (nameMatch && locationsMatch) {
|
|
149
|
-
const name = nameMatch[1];
|
|
150
|
-
const locations = locationsMatch?.[1]?.split(",").map((l) => l.trim().replace(/['"`]/g, "")).filter(Boolean).join(" | ") || "";
|
|
151
|
-
let args = "";
|
|
152
|
-
if (argsMatch && argsMatch[1] && argsMatch[1].trim()) {
|
|
153
|
-
const argDefs = [];
|
|
154
|
-
const argMatches = argsMatch[1].matchAll(/(\w+):\s*\{([^}]+)\}/g);
|
|
155
|
-
for (const argMatch of argMatches) {
|
|
156
|
-
const argName = argMatch[1];
|
|
157
|
-
const argBody = argMatch[2];
|
|
158
|
-
const typeMatch = argBody?.match(/type:\s*['"`](\[?\w+!?\]?)['"`]/);
|
|
159
|
-
const type = typeMatch ? typeMatch[1] : "String";
|
|
160
|
-
const defaultMatch = argBody?.match(/defaultValue:\s*(['"`]([^'"`]+)['"`]|(\d+)|true|false)/);
|
|
161
|
-
let defaultValue = "";
|
|
162
|
-
if (defaultMatch) {
|
|
163
|
-
const value = defaultMatch[2] || defaultMatch[3] || defaultMatch[1]?.replace(/['"`]/g, "");
|
|
164
|
-
if (type === "String") defaultValue = ` = "${value}"`;
|
|
165
|
-
else if (type === "Int" || type === "Float") defaultValue = ` = ${value}`;
|
|
166
|
-
else if (type === "Boolean") defaultValue = ` = ${value}`;
|
|
167
|
-
}
|
|
168
|
-
argDefs.push(`${argName}: ${type}${defaultValue}`);
|
|
169
|
-
}
|
|
170
|
-
if (argDefs.length > 0) args = `(${argDefs.join(", ")})`;
|
|
171
|
-
}
|
|
172
|
-
directiveSchemas.push(`directive @${name}${args} on ${locations}`);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (directiveSchemas.length > 0) {
|
|
176
|
-
const directivesPath = resolve(nitro.graphql.serverDir, "_directives.graphql");
|
|
177
|
-
const content = `# WARNING: This file is auto-generated by nitro-graphql
|
|
178
|
-
# Do not modify this file directly. It will be overwritten.
|
|
179
|
-
# To define custom directives, create .directive.ts files using defineDirective()
|
|
180
|
-
|
|
181
|
-
${directiveSchemas.join("\n\n")}`;
|
|
182
|
-
let shouldWrite = true;
|
|
183
|
-
if (existsSync(directivesPath)) {
|
|
184
|
-
const existingContent = readFileSync(directivesPath, "utf-8");
|
|
185
|
-
shouldWrite = existingContent !== content;
|
|
186
|
-
}
|
|
187
|
-
if (shouldWrite) writeFileSync(directivesPath, content, "utf-8");
|
|
188
|
-
if (!nitro.scanSchemas.includes(directivesPath)) nitro.scanSchemas.push(directivesPath);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
108
|
+
await generateDirectiveSchemas(nitro, directives$1);
|
|
191
109
|
const docs$1 = await scanDocs(nitro);
|
|
192
110
|
nitro.scanDocuments = docs$1;
|
|
193
111
|
});
|
|
@@ -258,8 +176,14 @@ ${directiveSchemas.join("\n\n")}`;
|
|
|
258
176
|
types.tsConfig.compilerOptions.paths["#graphql/server"] = [relativeWithDot(tsconfigDir, join(typesDir, "nitro-graphql-server.d.ts"))];
|
|
259
177
|
types.tsConfig.compilerOptions.paths["#graphql/client"] = [relativeWithDot(tsconfigDir, join(typesDir, "nitro-graphql-client.d.ts"))];
|
|
260
178
|
types.tsConfig.compilerOptions.paths["#graphql/schema"] = [relativeWithDot(tsconfigDir, join(nitro.graphql.serverDir, "schema.ts"))];
|
|
179
|
+
if (nitro.options.graphql?.externalServices?.length) for (const service of nitro.options.graphql.externalServices) types.tsConfig.compilerOptions.paths[`#graphql/client/${service.name}`] = [relativeWithDot(tsconfigDir, join(typesDir, `nitro-graphql-client-${service.name}.d.ts`))];
|
|
261
180
|
types.tsConfig.include = types.tsConfig.include || [];
|
|
262
181
|
types.tsConfig.include.push(relativeWithDot(tsconfigDir, join(typesDir, "nitro-graphql-server.d.ts")), relativeWithDot(tsconfigDir, join(typesDir, "nitro-graphql-client.d.ts")), relativeWithDot(tsconfigDir, join(typesDir, "graphql.d.ts")));
|
|
182
|
+
if (nitro.options.graphql?.externalServices?.length) for (const service of nitro.options.graphql.externalServices) types.tsConfig.include.push(relativeWithDot(tsconfigDir, join(typesDir, `nitro-graphql-client-${service.name}.d.ts`)));
|
|
183
|
+
});
|
|
184
|
+
if (nitro.options.framework?.name === "nuxt" && nitro.options.graphql?.externalServices?.length) nitro.hooks.hook("build:before", () => {
|
|
185
|
+
const nuxtOptions = nitro._nuxt?.options;
|
|
186
|
+
if (nuxtOptions) nuxtOptions.nitroGraphqlExternalServices = nitro.options.graphql?.externalServices || [];
|
|
263
187
|
});
|
|
264
188
|
if (!existsSync(join(nitro.options.rootDir, "graphql.config.ts"))) {
|
|
265
189
|
const schemaPath = relativeWithDot(nitro.options.rootDir, resolve(nitro.graphql.buildDir, "schema.graphql"));
|
package/dist/rollup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getImportId, scanGraphql } from "./utils/index.js";
|
|
2
2
|
import { clientTypeGeneration, serverTypeGeneration } from "./utils/type-generation.js";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
3
|
import { resolve } from "pathe";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { parse } from "graphql";
|
|
6
6
|
import { genImport } from "knitwork";
|
|
7
7
|
|