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 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
- ## 🚀 Quick Start
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:
@@ -1,7 +1,7 @@
1
- import * as _nuxt_schema0 from "@nuxt/schema";
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: _nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
5
+ declare const _default: _nuxt_schema3.NuxtModule<ModuleOptions, ModuleOptions, false>;
6
6
  //#endregion
7
7
  export { ModuleOptions, _default as default };
@@ -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 nitropack3 from "nitropack";
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: nitropack3.NitroModule;
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 { relativeWithDot, scanDirectives, scanDocs, scanResolvers, scanSchemas } from "./utils/index.js";
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, readFileSync, writeFileSync } from "node:fs";
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
- if (directives.length > 0) {
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
- if (directives$1.length > 0) {
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