nitro-graphql 1.1.3 → 1.2.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/README.md +258 -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 +41 -1
- package/dist/routes/apollo-server.d.ts +2 -2
- package/dist/routes/graphql-yoga.d.ts +2 -2
- package/dist/routes/health.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.js +4 -2
- package/dist/utils/index.d.ts +9 -1
- package/dist/utils/index.js +43 -2
- package/dist/utils/type-generation.js +145 -21
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -31,8 +31,22 @@
|
|
|
31
31
|
- 📦 **Optimized Bundling**: Smart chunking and dynamic imports for production
|
|
32
32
|
- 🌐 **Nuxt Integration**: First-class Nuxt.js support with dedicated module
|
|
33
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
|
|
34
35
|
|
|
35
|
-
##
|
|
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
|
|
36
50
|
|
|
37
51
|
### Step 1: Installation
|
|
38
52
|
|
|
@@ -1260,6 +1274,249 @@ Help us improve nitro-graphql! Pick any item and contribute:
|
|
|
1260
1274
|
> [!NOTE]
|
|
1261
1275
|
> Have other ideas? Open an issue to discuss!
|
|
1262
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
|
+
|
|
1263
1520
|
## 🛠️ VS Code Extensions
|
|
1264
1521
|
|
|
1265
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_schema1 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_schema1.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 nitropack0 from "nitropack";
|
|
4
4
|
|
|
5
5
|
//#region src/index.d.ts
|
|
6
|
-
declare const _default:
|
|
6
|
+
declare const _default: nitropack0.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,5 +1,5 @@
|
|
|
1
1
|
import { generateDirectiveSchemas } from "./utils/directive-parser.js";
|
|
2
|
-
import { relativeWithDot, scanDirectives, scanDocs, scanResolvers, scanSchemas } from "./utils/index.js";
|
|
2
|
+
import { relativeWithDot, scanDirectives, scanDocs, scanResolvers, scanSchemas, validateExternalServices } from "./utils/index.js";
|
|
3
3
|
import { clientTypeGeneration, serverTypeGeneration } from "./utils/type-generation.js";
|
|
4
4
|
import { rollupConfig } from "./rollup.js";
|
|
5
5
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
@@ -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,14 @@ 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
|
+
if (!pattern) continue;
|
|
75
|
+
const baseDir = pattern.split("**")[0]?.replace(/\/$/, "") || ".";
|
|
76
|
+
const resolvedDir = resolve(nitro.options.rootDir, baseDir);
|
|
77
|
+
if (!watchDirs.includes(resolvedDir)) watchDirs.push(resolvedDir);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
63
80
|
const watcher = watch(watchDirs, {
|
|
64
81
|
persistent: true,
|
|
65
82
|
ignoreInitial: true,
|
|
@@ -160,8 +177,14 @@ var src_default = defineNitroModule({
|
|
|
160
177
|
types.tsConfig.compilerOptions.paths["#graphql/server"] = [relativeWithDot(tsconfigDir, join(typesDir, "nitro-graphql-server.d.ts"))];
|
|
161
178
|
types.tsConfig.compilerOptions.paths["#graphql/client"] = [relativeWithDot(tsconfigDir, join(typesDir, "nitro-graphql-client.d.ts"))];
|
|
162
179
|
types.tsConfig.compilerOptions.paths["#graphql/schema"] = [relativeWithDot(tsconfigDir, join(nitro.graphql.serverDir, "schema.ts"))];
|
|
180
|
+
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`))];
|
|
163
181
|
types.tsConfig.include = types.tsConfig.include || [];
|
|
164
182
|
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")));
|
|
183
|
+
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`)));
|
|
184
|
+
});
|
|
185
|
+
if (nitro.options.framework?.name === "nuxt" && nitro.options.graphql?.externalServices?.length) nitro.hooks.hook("build:before", () => {
|
|
186
|
+
const nuxtOptions = nitro._nuxt?.options;
|
|
187
|
+
if (nuxtOptions) nuxtOptions.nitroGraphqlExternalServices = nitro.options.graphql?.externalServices || [];
|
|
165
188
|
});
|
|
166
189
|
if (!existsSync(join(nitro.options.rootDir, "graphql.config.ts"))) {
|
|
167
190
|
const schemaPath = relativeWithDot(nitro.options.rootDir, resolve(nitro.graphql.buildDir, "schema.graphql"));
|
|
@@ -223,6 +246,23 @@ declare module 'h3' {
|
|
|
223
246
|
consola.warn("nitro-graphql: Found context.d.ts file. Please rename it to context.ts for the new structure.");
|
|
224
247
|
consola.info("The context file should now be context.ts instead of context.d.ts");
|
|
225
248
|
}
|
|
249
|
+
if (nitro.options.framework.name === "nuxt") {
|
|
250
|
+
if (!existsSync(nitro.graphql.clientDir)) mkdirSync(nitro.graphql.clientDir, { recursive: true });
|
|
251
|
+
const defaultDir = join(nitro.graphql.clientDir, "default");
|
|
252
|
+
if (!existsSync(defaultDir)) mkdirSync(defaultDir, { recursive: true });
|
|
253
|
+
const sampleQueryFile = join(defaultDir, "queries.graphql");
|
|
254
|
+
if (!existsSync(sampleQueryFile)) writeFileSync(sampleQueryFile, `# Example GraphQL queries
|
|
255
|
+
# Add your GraphQL queries here
|
|
256
|
+
|
|
257
|
+
# query GetUser($id: ID!) {
|
|
258
|
+
# user(id: $id) {
|
|
259
|
+
# id
|
|
260
|
+
# name
|
|
261
|
+
# email
|
|
262
|
+
# }
|
|
263
|
+
# }
|
|
264
|
+
`, "utf-8");
|
|
265
|
+
}
|
|
226
266
|
}
|
|
227
267
|
});
|
|
228
268
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as h36 from "h3";
|
|
2
2
|
|
|
3
3
|
//#region src/routes/apollo-server.d.ts
|
|
4
|
-
declare const _default:
|
|
4
|
+
declare const _default: h36.EventHandler<h36.EventHandlerRequest, any>;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { _default as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as h32 from "h3";
|
|
2
2
|
|
|
3
3
|
//#region src/routes/graphql-yoga.d.ts
|
|
4
|
-
declare const _default:
|
|
4
|
+
declare const _default: h32.EventHandler<h32.EventHandlerRequest, Promise<Response>>;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { _default as default };
|
package/dist/routes/health.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as h34 from "h3";
|
|
2
2
|
|
|
3
3
|
//#region src/routes/health.d.ts
|
|
4
|
-
declare const _default:
|
|
4
|
+
declare const _default: h34.EventHandler<h34.EventHandlerRequest, Promise<{
|
|
5
5
|
status: string;
|
|
6
6
|
message: string;
|
|
7
7
|
timestamp: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -57,6 +57,33 @@ declare module 'nitropack' {
|
|
|
57
57
|
graphql?: NitroGraphQLOptions;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
interface ExternalGraphQLService {
|
|
61
|
+
/** Unique name for this service (used for file naming and type generation) */
|
|
62
|
+
name: string;
|
|
63
|
+
/** Schema source - can be URL(s) for remote schemas or file path(s) for local schemas */
|
|
64
|
+
schema: string | string[];
|
|
65
|
+
/** GraphQL endpoint for this service */
|
|
66
|
+
endpoint: string;
|
|
67
|
+
/** Optional headers for schema introspection and client requests */
|
|
68
|
+
headers?: Record<string, string> | (() => Record<string, string>);
|
|
69
|
+
/** Optional: specific document patterns for this service */
|
|
70
|
+
documents?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* Optional: Download and cache schema locally for offline usage
|
|
73
|
+
* - true or 'once': Download if file doesn't exist, then use cached version (offline-friendly)
|
|
74
|
+
* - 'always': Check for updates on every build (current behavior)
|
|
75
|
+
* - 'manual': Never download automatically, user manages schema files manually
|
|
76
|
+
* - false: Disable schema downloading
|
|
77
|
+
*/
|
|
78
|
+
downloadSchema?: boolean | 'once' | 'always' | 'manual';
|
|
79
|
+
/** Optional: Custom path to save downloaded schema (default: .nitro/graphql/schemas/[serviceName].graphql) */
|
|
80
|
+
downloadPath?: string;
|
|
81
|
+
/** Optional: service-specific codegen configuration */
|
|
82
|
+
codegen?: {
|
|
83
|
+
client?: CodegenClientConfig;
|
|
84
|
+
clientSDK?: GenericSdkConfig;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
60
87
|
interface NitroGraphQLOptions {
|
|
61
88
|
framework: 'graphql-yoga' | 'apollo-server';
|
|
62
89
|
endpoint?: {
|
|
@@ -76,6 +103,8 @@ interface NitroGraphQLOptions {
|
|
|
76
103
|
client?: CodegenClientConfig;
|
|
77
104
|
clientSDK?: GenericSdkConfig;
|
|
78
105
|
};
|
|
106
|
+
/** External GraphQL services to generate types and SDKs for */
|
|
107
|
+
externalServices?: ExternalGraphQLService[];
|
|
79
108
|
}
|
|
80
109
|
//#endregion
|
|
81
|
-
export { CodegenClientConfig, CodegenServerConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions };
|
|
110
|
+
export { CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, GenImport, GenericSdkConfig, NitroGraphQLOptions };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CodegenClientConfig, GenericSdkConfig } from "../types/index.js";
|
|
1
|
+
import { CodegenClientConfig, ExternalGraphQLService, GenericSdkConfig } from "../types/index.js";
|
|
2
2
|
import { GraphQLSchema } from "graphql";
|
|
3
3
|
import { Source } from "@graphql-tools/utils";
|
|
4
4
|
import { LoadSchemaOptions, UnnormalizedTypeDefPointer } from "@graphql-tools/load";
|
|
@@ -14,10 +14,25 @@ type GraphQLTypeDefPointer = UnnormalizedTypeDefPointer | UnnormalizedTypeDefPoi
|
|
|
14
14
|
*/
|
|
15
15
|
type GraphQLLoadSchemaOptions = Partial<LoadSchemaOptions>;
|
|
16
16
|
declare function graphQLLoadSchemaSync(schemaPointers: GraphQLTypeDefPointer, data?: GraphQLLoadSchemaOptions): Promise<GraphQLSchema | undefined>;
|
|
17
|
+
/**
|
|
18
|
+
* Load schema from external GraphQL service
|
|
19
|
+
*/
|
|
20
|
+
declare function loadExternalSchema(service: ExternalGraphQLService, buildDir?: string): Promise<GraphQLSchema | undefined>;
|
|
21
|
+
/**
|
|
22
|
+
* Download and save schema from external service
|
|
23
|
+
*/
|
|
24
|
+
declare function downloadAndSaveSchema(service: ExternalGraphQLService, buildDir: string): Promise<string | undefined>;
|
|
17
25
|
declare function loadGraphQLDocuments(patterns: string | string[]): Promise<Source[]>;
|
|
18
|
-
declare function generateClientTypes(schema: GraphQLSchema, docs: Source[], config?: CodegenClientConfig, sdkConfig?: GenericSdkConfig, outputPath?: string): Promise<false | {
|
|
26
|
+
declare function generateClientTypes(schema: GraphQLSchema, docs: Source[], config?: CodegenClientConfig, sdkConfig?: GenericSdkConfig, outputPath?: string, serviceName?: string): Promise<false | {
|
|
19
27
|
types: string;
|
|
20
28
|
sdk: string;
|
|
21
29
|
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Generate client types for external GraphQL service
|
|
32
|
+
*/
|
|
33
|
+
declare function generateExternalClientTypes(service: ExternalGraphQLService, schema: GraphQLSchema, docs: Source[]): Promise<{
|
|
34
|
+
types: string;
|
|
35
|
+
sdk: string;
|
|
36
|
+
} | false>;
|
|
22
37
|
//#endregion
|
|
23
|
-
export { GraphQLLoadSchemaOptions, GraphQLTypeDefPointer, generateClientTypes, graphQLLoadSchemaSync, loadGraphQLDocuments };
|
|
38
|
+
export { GraphQLLoadSchemaOptions, GraphQLTypeDefPointer, downloadAndSaveSchema, generateClientTypes, generateExternalClientTypes, graphQLLoadSchemaSync, loadExternalSchema, loadGraphQLDocuments };
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { preset } from "../node_modules/.pnpm/@graphql-codegen_import-types-preset@3.0.1_graphql@16.11.0/node_modules/@graphql-codegen/import-types-preset/esm/index.js";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { consola as consola$1 } from "consola";
|
|
3
4
|
import { defu as defu$1 } from "defu";
|
|
5
|
+
import { dirname, resolve } from "pathe";
|
|
4
6
|
import { parse } from "graphql";
|
|
5
7
|
import { printSchemaWithDirectives } from "@graphql-tools/utils";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
6
9
|
import { codegen } from "@graphql-codegen/core";
|
|
7
10
|
import { plugin } from "@graphql-codegen/typescript";
|
|
8
11
|
import { plugin as plugin$1 } from "@graphql-codegen/typescript-generic-sdk";
|
|
9
12
|
import { plugin as plugin$2 } from "@graphql-codegen/typescript-operations";
|
|
10
13
|
import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
|
|
11
14
|
import { loadDocuments, loadSchemaSync } from "@graphql-tools/load";
|
|
15
|
+
import { UrlLoader } from "@graphql-tools/url-loader";
|
|
12
16
|
import { CurrencyResolver, DateTimeISOResolver, DateTimeResolver, JSONObjectResolver, JSONResolver, NonEmptyStringResolver, UUIDResolver } from "graphql-scalars";
|
|
13
17
|
|
|
14
18
|
//#region src/utils/client-codegen.ts
|
|
@@ -31,7 +35,11 @@ async function graphQLLoadSchemaSync(schemaPointers, data = {}) {
|
|
|
31
35
|
try {
|
|
32
36
|
result = loadSchemaSync(filteredPointers, {
|
|
33
37
|
...data,
|
|
34
|
-
loaders: [
|
|
38
|
+
loaders: [
|
|
39
|
+
new GraphQLFileLoader(),
|
|
40
|
+
new UrlLoader(),
|
|
41
|
+
...data.loaders || []
|
|
42
|
+
]
|
|
35
43
|
});
|
|
36
44
|
} catch (e) {
|
|
37
45
|
if ((e.message || "").includes("Unable to find any GraphQL type definitions for the following pointers:")) consola$1.info("No server GraphQL files found. If you need server-side GraphQL, add .graphql files to your server directory.");
|
|
@@ -39,6 +47,92 @@ async function graphQLLoadSchemaSync(schemaPointers, data = {}) {
|
|
|
39
47
|
}
|
|
40
48
|
return result;
|
|
41
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Load schema from external GraphQL service
|
|
52
|
+
*/
|
|
53
|
+
async function loadExternalSchema(service, buildDir) {
|
|
54
|
+
try {
|
|
55
|
+
const headers = typeof service.headers === "function" ? service.headers() : service.headers || {};
|
|
56
|
+
const schemas = Array.isArray(service.schema) ? service.schema : [service.schema];
|
|
57
|
+
if (service.downloadSchema && buildDir) {
|
|
58
|
+
const defaultPath = resolve(buildDir, "graphql", "schemas", `${service.name}.graphql`);
|
|
59
|
+
const schemaFilePath = service.downloadPath ? resolve(service.downloadPath) : defaultPath;
|
|
60
|
+
if (existsSync(schemaFilePath)) {
|
|
61
|
+
consola$1.info(`[graphql:${service.name}] Loading schema from local file: ${schemaFilePath}`);
|
|
62
|
+
try {
|
|
63
|
+
const result$1 = loadSchemaSync([schemaFilePath], { loaders: [new GraphQLFileLoader()] });
|
|
64
|
+
consola$1.info(`[graphql:${service.name}] External schema loaded successfully from local file`);
|
|
65
|
+
return result$1;
|
|
66
|
+
} catch (localError) {
|
|
67
|
+
consola$1.warn(`[graphql:${service.name}] Failed to load local schema, falling back to remote:`, localError);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
consola$1.info(`[graphql:${service.name}] Loading external schema from: ${schemas.join(", ")}`);
|
|
72
|
+
const result = loadSchemaSync(schemas, {
|
|
73
|
+
loaders: [new GraphQLFileLoader(), new UrlLoader()],
|
|
74
|
+
...Object.keys(headers).length > 0 && { headers }
|
|
75
|
+
});
|
|
76
|
+
consola$1.info(`[graphql:${service.name}] External schema loaded successfully`);
|
|
77
|
+
return result;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
consola$1.error(`[graphql:${service.name}] Failed to load external schema:`, error);
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Download and save schema from external service
|
|
85
|
+
*/
|
|
86
|
+
async function downloadAndSaveSchema(service, buildDir) {
|
|
87
|
+
const downloadMode = service.downloadSchema;
|
|
88
|
+
if (!downloadMode || downloadMode === "manual") return void 0;
|
|
89
|
+
const defaultPath = resolve(buildDir, "graphql", "schemas", `${service.name}.graphql`);
|
|
90
|
+
const schemaFilePath = service.downloadPath ? resolve(service.downloadPath) : defaultPath;
|
|
91
|
+
try {
|
|
92
|
+
const headers = typeof service.headers === "function" ? service.headers() : service.headers || {};
|
|
93
|
+
const schemas = Array.isArray(service.schema) ? service.schema : [service.schema];
|
|
94
|
+
let shouldDownload = false;
|
|
95
|
+
const fileExists = existsSync(schemaFilePath);
|
|
96
|
+
if (downloadMode === "always") {
|
|
97
|
+
shouldDownload = true;
|
|
98
|
+
if (fileExists) try {
|
|
99
|
+
const remoteSchema = loadSchemaSync(schemas, {
|
|
100
|
+
loaders: [new UrlLoader()],
|
|
101
|
+
...Object.keys(headers).length > 0 && { headers }
|
|
102
|
+
});
|
|
103
|
+
const remoteSchemaString = printSchemaWithDirectives(remoteSchema);
|
|
104
|
+
const remoteHash = createHash("md5").update(remoteSchemaString).digest("hex");
|
|
105
|
+
const localSchemaString = readFileSync(schemaFilePath, "utf-8");
|
|
106
|
+
const localHash = createHash("md5").update(localSchemaString).digest("hex");
|
|
107
|
+
if (remoteHash === localHash) {
|
|
108
|
+
shouldDownload = false;
|
|
109
|
+
consola$1.info(`[graphql:${service.name}] Schema is up-to-date, using cached version`);
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
consola$1.warn(`[graphql:${service.name}] Unable to compare with remote schema, updating local cache`);
|
|
113
|
+
shouldDownload = true;
|
|
114
|
+
}
|
|
115
|
+
} else if (downloadMode === true || downloadMode === "once") {
|
|
116
|
+
shouldDownload = !fileExists;
|
|
117
|
+
if (fileExists) consola$1.info(`[graphql:${service.name}] Using cached schema from: ${schemaFilePath}`);
|
|
118
|
+
}
|
|
119
|
+
if (shouldDownload) {
|
|
120
|
+
consola$1.info(`[graphql:${service.name}] Downloading schema to: ${schemaFilePath}`);
|
|
121
|
+
const schema = loadSchemaSync(schemas, {
|
|
122
|
+
loaders: [new UrlLoader()],
|
|
123
|
+
...Object.keys(headers).length > 0 && { headers }
|
|
124
|
+
});
|
|
125
|
+
const schemaString = printSchemaWithDirectives(schema);
|
|
126
|
+
mkdirSync(dirname(schemaFilePath), { recursive: true });
|
|
127
|
+
writeFileSync(schemaFilePath, schemaString, "utf-8");
|
|
128
|
+
consola$1.success(`[graphql:${service.name}] Schema downloaded and saved successfully`);
|
|
129
|
+
}
|
|
130
|
+
return schemaFilePath;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
consola$1.error(`[graphql:${service.name}] Failed to download schema:`, error);
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
42
136
|
async function loadGraphQLDocuments(patterns) {
|
|
43
137
|
try {
|
|
44
138
|
const result = await loadDocuments(patterns, { loaders: [new GraphQLFileLoader()] });
|
|
@@ -48,12 +142,14 @@ async function loadGraphQLDocuments(patterns) {
|
|
|
48
142
|
else throw e;
|
|
49
143
|
}
|
|
50
144
|
}
|
|
51
|
-
async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, outputPath) {
|
|
145
|
+
async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, outputPath, serviceName) {
|
|
52
146
|
if (docs.length === 0) {
|
|
53
|
-
|
|
147
|
+
const serviceLabel$1 = serviceName ? `:${serviceName}` : "";
|
|
148
|
+
consola$1.info(`[graphql${serviceLabel$1}] No client GraphQL files found. Skipping client type generation.`);
|
|
54
149
|
return false;
|
|
55
150
|
}
|
|
56
|
-
|
|
151
|
+
const serviceLabel = serviceName ? `:${serviceName}` : "";
|
|
152
|
+
consola$1.info(`[graphql${serviceLabel}] Found ${docs.length} client GraphQL documents`);
|
|
57
153
|
const defaultConfig = {
|
|
58
154
|
emitLegacyCommonJSImports: false,
|
|
59
155
|
useTypeImports: true,
|
|
@@ -98,12 +194,13 @@ async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, ou
|
|
|
98
194
|
typescriptOperations: { plugin: plugin$2 }
|
|
99
195
|
}
|
|
100
196
|
});
|
|
197
|
+
const typesPath = serviceName ? `#graphql/client/${serviceName}` : "#graphql/client";
|
|
101
198
|
const sdkOutput = await preset.buildGeneratesSection({
|
|
102
199
|
baseOutputDir: outputPath || "client-types.generated.ts",
|
|
103
200
|
schema: parse(printSchemaWithDirectives(schema)),
|
|
104
201
|
documents: [...docs],
|
|
105
202
|
config: mergedSdkConfig,
|
|
106
|
-
presetConfig: { typesPath
|
|
203
|
+
presetConfig: { typesPath },
|
|
107
204
|
plugins: [{ pluginContent: {} }, { typescriptGenericSdk: {} }],
|
|
108
205
|
pluginMap: {
|
|
109
206
|
pluginContent: { plugin: pluginContent },
|
|
@@ -116,15 +213,24 @@ async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, ou
|
|
|
116
213
|
content: await codegen(config$1)
|
|
117
214
|
};
|
|
118
215
|
}));
|
|
216
|
+
const sdkContent = results[0]?.content || "";
|
|
119
217
|
return {
|
|
120
218
|
types: output,
|
|
121
|
-
sdk:
|
|
219
|
+
sdk: sdkContent
|
|
122
220
|
};
|
|
123
221
|
} catch (error) {
|
|
124
|
-
consola$1.warn(
|
|
222
|
+
consola$1.warn(`[graphql${serviceLabel}] Client type generation failed:`, error);
|
|
125
223
|
return false;
|
|
126
224
|
}
|
|
127
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Generate client types for external GraphQL service
|
|
228
|
+
*/
|
|
229
|
+
async function generateExternalClientTypes(service, schema, docs) {
|
|
230
|
+
const config = service.codegen?.client || {};
|
|
231
|
+
const sdkConfig = service.codegen?.clientSDK || {};
|
|
232
|
+
return generateClientTypes(schema, docs, config, sdkConfig, void 0, service.name);
|
|
233
|
+
}
|
|
128
234
|
|
|
129
235
|
//#endregion
|
|
130
|
-
export { generateClientTypes, graphQLLoadSchemaSync, loadGraphQLDocuments };
|
|
236
|
+
export { downloadAndSaveSchema, generateClientTypes, generateExternalClientTypes, graphQLLoadSchemaSync, loadExternalSchema, loadGraphQLDocuments };
|
|
@@ -149,10 +149,11 @@ var DirectiveParser = class {
|
|
|
149
149
|
for (const prop of node.properties || []) {
|
|
150
150
|
if (prop.type !== "Property" || prop.key?.type !== "Identifier") continue;
|
|
151
151
|
switch (prop.key.name) {
|
|
152
|
-
case "type":
|
|
152
|
+
case "type": {
|
|
153
153
|
const typeValue = this.extractStringLiteral(prop.value);
|
|
154
154
|
if (typeValue) type = typeValue;
|
|
155
155
|
break;
|
|
156
|
+
}
|
|
156
157
|
case "defaultValue":
|
|
157
158
|
defaultValue = this.extractLiteralValue(prop.value);
|
|
158
159
|
break;
|
|
@@ -198,9 +199,10 @@ async function generateDirectiveSchemas(nitro, directives) {
|
|
|
198
199
|
const { resolve } = await import("pathe");
|
|
199
200
|
const directiveSchemas = [];
|
|
200
201
|
const seenDirectives = /* @__PURE__ */ new Set();
|
|
202
|
+
const parser = new DirectiveParser();
|
|
201
203
|
for (const dir of directives) for (const _imp of dir.imports) {
|
|
202
204
|
const fileContent = await readFile(dir.specifier, "utf-8");
|
|
203
|
-
const directiveDefs = await
|
|
205
|
+
const directiveDefs = await parser.parseDirectives(fileContent, dir.specifier);
|
|
204
206
|
for (const def of directiveDefs) {
|
|
205
207
|
if (seenDirectives.has(def.name)) continue;
|
|
206
208
|
seenDirectives.add(def.name);
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -12,5 +12,13 @@ declare function scanDirectives(nitro: Nitro): Promise<GenImport[]>;
|
|
|
12
12
|
declare function scanTypeDefs(nitro: Nitro): Promise<string[]>;
|
|
13
13
|
declare function scanSchemas(nitro: Nitro): Promise<string[]>;
|
|
14
14
|
declare function scanDocs(nitro: Nitro): Promise<string[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Scan documents for a specific external service
|
|
17
|
+
*/
|
|
18
|
+
declare function scanExternalServiceDocs(nitro: Nitro, serviceName: string, patterns: string[]): Promise<string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Validate external GraphQL service configuration
|
|
21
|
+
*/
|
|
22
|
+
declare function validateExternalServices(services: any[]): string[];
|
|
15
23
|
//#endregion
|
|
16
|
-
export { GLOB_SCAN_PATTERN, directiveParser, generateDirectiveSchema, generateDirectiveSchemas, getImportId, relativeWithDot, scanDirectives, scanDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs };
|
|
24
|
+
export { GLOB_SCAN_PATTERN, directiveParser, generateDirectiveSchema, generateDirectiveSchemas, getImportId, relativeWithDot, scanDirectives, scanDocs, scanExternalServiceDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs, validateExternalServices };
|
package/dist/utils/index.js
CHANGED
|
@@ -104,7 +104,48 @@ async function scanSchemas(nitro) {
|
|
|
104
104
|
}
|
|
105
105
|
async function scanDocs(nitro) {
|
|
106
106
|
const files = await scanDir(nitro, nitro.options.rootDir, nitro.graphql.dir.client, "**/*.graphql");
|
|
107
|
-
return files.map((f) => f.fullPath);
|
|
107
|
+
return files.filter((f) => !f.path.startsWith("external/")).map((f) => f.fullPath);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Scan documents for a specific external service
|
|
111
|
+
*/
|
|
112
|
+
async function scanExternalServiceDocs(nitro, serviceName, patterns) {
|
|
113
|
+
if (!patterns.length) return [];
|
|
114
|
+
const files = [];
|
|
115
|
+
for (const pattern of patterns) try {
|
|
116
|
+
const serviceFiles = await glob(pattern, {
|
|
117
|
+
cwd: nitro.options.rootDir,
|
|
118
|
+
dot: true,
|
|
119
|
+
ignore: nitro.options.ignore,
|
|
120
|
+
absolute: true
|
|
121
|
+
});
|
|
122
|
+
files.push(...serviceFiles);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
nitro.logger.warn(`[graphql:${serviceName}] Error scanning documents with pattern "${pattern}":`, error);
|
|
125
|
+
}
|
|
126
|
+
return files.filter((file, index, self) => self.indexOf(file) === index);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Validate external GraphQL service configuration
|
|
130
|
+
*/
|
|
131
|
+
function validateExternalServices(services) {
|
|
132
|
+
const errors = [];
|
|
133
|
+
const serviceNames = /* @__PURE__ */ new Set();
|
|
134
|
+
for (const [index, service] of services.entries()) {
|
|
135
|
+
const prefix = `externalServices[${index}]`;
|
|
136
|
+
if (!service.name || typeof service.name !== "string") errors.push(`${prefix}.name is required and must be a string`);
|
|
137
|
+
else if (serviceNames.has(service.name)) errors.push(`${prefix}.name "${service.name}" must be unique`);
|
|
138
|
+
else serviceNames.add(service.name);
|
|
139
|
+
if (!service.schema) errors.push(`${prefix}.schema is required`);
|
|
140
|
+
if (!service.endpoint || typeof service.endpoint !== "string") errors.push(`${prefix}.endpoint is required and must be a string`);
|
|
141
|
+
else try {
|
|
142
|
+
const url = new URL(service.endpoint);
|
|
143
|
+
} catch {
|
|
144
|
+
errors.push(`${prefix}.endpoint "${service.endpoint}" must be a valid URL`);
|
|
145
|
+
}
|
|
146
|
+
if (service.name && !/^[a-z]\w*$/i.test(service.name)) errors.push(`${prefix}.name "${service.name}" must be a valid identifier (letters, numbers, underscore, starting with letter)`);
|
|
147
|
+
}
|
|
148
|
+
return errors;
|
|
108
149
|
}
|
|
109
150
|
async function scanFiles(nitro, name, globPattern = GLOB_SCAN_PATTERN) {
|
|
110
151
|
const files = await Promise.all(nitro.options.scanDirs.map((dir) => scanDir(nitro, dir, name, globPattern))).then((r) => r.flat());
|
|
@@ -132,4 +173,4 @@ async function scanDir(nitro, dir, name, globPattern = GLOB_SCAN_PATTERN) {
|
|
|
132
173
|
}
|
|
133
174
|
|
|
134
175
|
//#endregion
|
|
135
|
-
export { GLOB_SCAN_PATTERN, directiveParser, generateDirectiveSchema, generateDirectiveSchemas, getImportId, relativeWithDot, scanDirectives, scanDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs };
|
|
176
|
+
export { GLOB_SCAN_PATTERN, directiveParser, generateDirectiveSchema, generateDirectiveSchemas, getImportId, relativeWithDot, scanDirectives, scanDocs, scanExternalServiceDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs, validateExternalServices };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateClientTypes, loadGraphQLDocuments } from "./client-codegen.js";
|
|
1
|
+
import { downloadAndSaveSchema, generateClientTypes, generateExternalClientTypes, loadExternalSchema, loadGraphQLDocuments } from "./client-codegen.js";
|
|
2
2
|
import { generateTypes } from "./server-codegen.js";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
import consola from "consola";
|
|
@@ -9,8 +9,27 @@ import { mergeTypeDefs } from "@graphql-tools/merge";
|
|
|
9
9
|
import { printSchemaWithDirectives } from "@graphql-tools/utils";
|
|
10
10
|
|
|
11
11
|
//#region src/utils/type-generation.ts
|
|
12
|
-
function
|
|
13
|
-
const
|
|
12
|
+
function generateGraphQLIndexFile(clientDir, externalServices = []) {
|
|
13
|
+
const indexPath = resolve(clientDir, "index.ts");
|
|
14
|
+
if (!existsSync(indexPath)) {
|
|
15
|
+
let indexContent = `// This file is auto-generated once by nitro-graphql for quick start
|
|
16
|
+
// You can modify this file according to your needs
|
|
17
|
+
//
|
|
18
|
+
// Export your main GraphQL service (auto-generated)
|
|
19
|
+
export * from './default/ofetch'
|
|
20
|
+
|
|
21
|
+
// Export external GraphQL services (auto-generated for existing services)
|
|
22
|
+
// When you add new external services, don't forget to add their exports here:
|
|
23
|
+
// export * from './yourServiceName/ofetch'
|
|
24
|
+
`;
|
|
25
|
+
for (const service of externalServices) indexContent += `export * from './${service.name}/ofetch'\n`;
|
|
26
|
+
writeFileSync(indexPath, indexContent, "utf-8");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function generateNuxtOfetchClient(clientDir, serviceName = "default") {
|
|
30
|
+
const serviceDir = resolve(clientDir, serviceName);
|
|
31
|
+
const ofetchPath = resolve(serviceDir, "ofetch.ts");
|
|
32
|
+
if (!existsSync(serviceDir)) mkdirSync(serviceDir, { recursive: true });
|
|
14
33
|
if (!existsSync(ofetchPath)) {
|
|
15
34
|
const ofetchContent = `// This file is auto-generated once by nitro-graphql for quick start
|
|
16
35
|
// You can modify this file according to your needs
|
|
@@ -38,6 +57,38 @@ export const $sdk = getSdk(createGraphQLClient('/api/graphql'))`;
|
|
|
38
57
|
writeFileSync(ofetchPath, ofetchContent, "utf-8");
|
|
39
58
|
}
|
|
40
59
|
}
|
|
60
|
+
function generateExternalOfetchClient(clientDir, serviceName, endpoint) {
|
|
61
|
+
const serviceDir = resolve(clientDir, serviceName);
|
|
62
|
+
const ofetchPath = resolve(serviceDir, "ofetch.ts");
|
|
63
|
+
if (!existsSync(serviceDir)) mkdirSync(serviceDir, { recursive: true });
|
|
64
|
+
if (!existsSync(ofetchPath)) {
|
|
65
|
+
const capitalizedServiceName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1);
|
|
66
|
+
const ofetchContent = `// This file is auto-generated once by nitro-graphql for quick start
|
|
67
|
+
// You can modify this file according to your needs
|
|
68
|
+
import type { Sdk, Requester } from './sdk'
|
|
69
|
+
import { getSdk } from './sdk'
|
|
70
|
+
|
|
71
|
+
export function create${capitalizedServiceName}GraphQLClient(endpoint: string = '${endpoint}'): Requester {
|
|
72
|
+
return async <R>(doc: string, vars?: any): Promise<R> => {
|
|
73
|
+
const headers = import.meta.server ? useRequestHeaders() : undefined
|
|
74
|
+
|
|
75
|
+
const result = await $fetch(endpoint, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: { query: doc, variables: vars },
|
|
78
|
+
headers: {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
...headers,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return result as R
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const $${serviceName}Sdk: Sdk = getSdk(create${capitalizedServiceName}GraphQLClient())`;
|
|
89
|
+
writeFileSync(ofetchPath, ofetchContent, "utf-8");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
41
92
|
async function serverTypeGeneration(app) {
|
|
42
93
|
try {
|
|
43
94
|
const schemas = app.scanSchemas || [];
|
|
@@ -65,28 +116,101 @@ async function serverTypeGeneration(app) {
|
|
|
65
116
|
}
|
|
66
117
|
async function clientTypeGeneration(nitro) {
|
|
67
118
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const schemaFilePath = join(nitro.graphql.buildDir, "schema.graphql");
|
|
71
|
-
if (!existsSync(schemaFilePath)) {
|
|
72
|
-
consola.info("Schema file not ready yet for client type generation. Server types need to be generated first.");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const graphqlString = readFileSync(schemaFilePath, "utf-8");
|
|
76
|
-
const schema = buildSchema(graphqlString);
|
|
77
|
-
const types = await generateClientTypes(schema, loadDocs, nitro.options.graphql?.codegen?.client ?? {}, nitro.options.graphql?.codegen?.clientSDK ?? {});
|
|
78
|
-
if (types === false) return;
|
|
79
|
-
const clientTypesPath = resolve(nitro.options.buildDir, "types", "nitro-graphql-client.d.ts");
|
|
80
|
-
const sdkTypesPath = resolve(nitro.graphql.clientDir, "sdk.ts");
|
|
81
|
-
mkdirSync(dirname(clientTypesPath), { recursive: true });
|
|
82
|
-
writeFileSync(clientTypesPath, types.types, "utf-8");
|
|
83
|
-
mkdirSync(dirname(sdkTypesPath), { recursive: true });
|
|
84
|
-
writeFileSync(sdkTypesPath, types.sdk, "utf-8");
|
|
85
|
-
if (nitro.options.framework?.name === "nuxt") generateNuxtOfetchClient(nitro.graphql.clientDir);
|
|
119
|
+
await generateMainClientTypes(nitro);
|
|
120
|
+
if (nitro.options.graphql?.externalServices?.length) await generateExternalServicesTypes(nitro);
|
|
86
121
|
} catch (error) {
|
|
87
122
|
consola.error("Client schema generation error:", error);
|
|
88
123
|
}
|
|
89
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Check for old structure files and warn user about manual migration
|
|
127
|
+
*/
|
|
128
|
+
function checkOldStructure(clientDir) {
|
|
129
|
+
const oldOfetchPath = resolve(clientDir, "ofetch.ts");
|
|
130
|
+
const oldSdkPath = resolve(clientDir, "sdk.ts");
|
|
131
|
+
if (existsSync(oldOfetchPath) || existsSync(oldSdkPath)) {
|
|
132
|
+
const foundFiles = [];
|
|
133
|
+
if (existsSync(oldOfetchPath)) foundFiles.push("app/graphql/ofetch.ts");
|
|
134
|
+
if (existsSync(oldSdkPath)) foundFiles.push("app/graphql/sdk.ts");
|
|
135
|
+
consola.error(`⚠️ OLD GRAPHQL STRUCTURE DETECTED!
|
|
136
|
+
|
|
137
|
+
📁 Found old files in app/graphql/ directory that need to be moved:
|
|
138
|
+
• ${foundFiles.join("\n • ")}
|
|
139
|
+
|
|
140
|
+
🔄 Please manually move these files to the new structure:
|
|
141
|
+
• app/graphql/ofetch.ts → app/graphql/default/ofetch.ts
|
|
142
|
+
• app/graphql/sdk.ts → app/graphql/default/sdk.ts
|
|
143
|
+
|
|
144
|
+
📝 Also update your app/graphql/index.ts to include:
|
|
145
|
+
export * from './default/ofetch'
|
|
146
|
+
|
|
147
|
+
💡 After moving, update your imports to use:
|
|
148
|
+
import { $sdk } from "#graphql/client"
|
|
149
|
+
|
|
150
|
+
🚫 The old files will cause import conflicts until moved!`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function generateMainClientTypes(nitro) {
|
|
154
|
+
checkOldStructure(nitro.graphql.clientDir);
|
|
155
|
+
const docs = nitro.scanDocuments;
|
|
156
|
+
const loadDocs = await loadGraphQLDocuments(docs);
|
|
157
|
+
const schemaFilePath = join(nitro.graphql.buildDir, "schema.graphql");
|
|
158
|
+
if (!existsSync(schemaFilePath)) {
|
|
159
|
+
consola.info("Schema file not ready yet for client type generation. Server types need to be generated first.");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const graphqlString = readFileSync(schemaFilePath, "utf-8");
|
|
163
|
+
const schema = buildSchema(graphqlString);
|
|
164
|
+
const types = await generateClientTypes(schema, loadDocs, nitro.options.graphql?.codegen?.client ?? {}, nitro.options.graphql?.codegen?.clientSDK ?? {});
|
|
165
|
+
if (types === false) return;
|
|
166
|
+
const clientTypesPath = resolve(nitro.options.buildDir, "types", "nitro-graphql-client.d.ts");
|
|
167
|
+
const defaultServiceDir = resolve(nitro.graphql.clientDir, "default");
|
|
168
|
+
const sdkTypesPath = resolve(defaultServiceDir, "sdk.ts");
|
|
169
|
+
mkdirSync(dirname(clientTypesPath), { recursive: true });
|
|
170
|
+
writeFileSync(clientTypesPath, types.types, "utf-8");
|
|
171
|
+
mkdirSync(defaultServiceDir, { recursive: true });
|
|
172
|
+
writeFileSync(sdkTypesPath, types.sdk, "utf-8");
|
|
173
|
+
if (nitro.options.framework?.name === "nuxt") {
|
|
174
|
+
generateNuxtOfetchClient(nitro.graphql.clientDir, "default");
|
|
175
|
+
const externalServices = nitro.options.graphql?.externalServices || [];
|
|
176
|
+
generateGraphQLIndexFile(nitro.graphql.clientDir, externalServices);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function generateExternalServicesTypes(nitro) {
|
|
180
|
+
const externalServices = nitro.options.graphql?.externalServices || [];
|
|
181
|
+
for (const service of externalServices) try {
|
|
182
|
+
consola.info(`[graphql:${service.name}] Processing external service`);
|
|
183
|
+
await downloadAndSaveSchema(service, nitro.options.buildDir);
|
|
184
|
+
const schema = await loadExternalSchema(service, nitro.options.buildDir);
|
|
185
|
+
if (!schema) {
|
|
186
|
+
consola.warn(`[graphql:${service.name}] Failed to load schema, skipping`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const documentPatterns = service.documents || [];
|
|
190
|
+
let loadDocs = [];
|
|
191
|
+
if (documentPatterns.length > 0) try {
|
|
192
|
+
loadDocs = await loadGraphQLDocuments(documentPatterns);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
consola.warn(`[graphql:${service.name}] No documents found:`, error);
|
|
195
|
+
}
|
|
196
|
+
const types = await generateExternalClientTypes(service, schema, loadDocs);
|
|
197
|
+
if (types === false) {
|
|
198
|
+
consola.warn(`[graphql:${service.name}] Type generation failed`);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const serviceTypesPath = resolve(nitro.options.buildDir, "types", `nitro-graphql-client-${service.name}.d.ts`);
|
|
202
|
+
const serviceDir = resolve(nitro.graphql.clientDir, service.name);
|
|
203
|
+
const serviceSdkPath = resolve(serviceDir, "sdk.ts");
|
|
204
|
+
mkdirSync(dirname(serviceTypesPath), { recursive: true });
|
|
205
|
+
writeFileSync(serviceTypesPath, types.types, "utf-8");
|
|
206
|
+
mkdirSync(serviceDir, { recursive: true });
|
|
207
|
+
writeFileSync(serviceSdkPath, types.sdk, "utf-8");
|
|
208
|
+
if (nitro.options.framework?.name === "nuxt") generateExternalOfetchClient(nitro.graphql.clientDir, service.name, service.endpoint);
|
|
209
|
+
consola.success(`[graphql:${service.name}] External service types generated successfully`);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
consola.error(`[graphql:${service.name}] External service generation failed:`, error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
90
214
|
|
|
91
215
|
//#endregion
|
|
92
216
|
export { clientTypeGeneration, serverTypeGeneration };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-graphql",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.1
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"description": "GraphQL integration for Nitro",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"@graphql-tools/load-files": "^7.0.1",
|
|
73
73
|
"@graphql-tools/merge": "^9.1.0",
|
|
74
74
|
"@graphql-tools/schema": "^10.0.24",
|
|
75
|
+
"@graphql-tools/url-loader": "^8.0.33",
|
|
75
76
|
"@graphql-tools/utils": "^10.9.0",
|
|
76
77
|
"chokidar": "^4.0.3",
|
|
77
78
|
"consola": "^3.4.2",
|
|
@@ -88,8 +89,8 @@
|
|
|
88
89
|
"@antfu/eslint-config": "^4.17.0",
|
|
89
90
|
"@apollo/server": "^5.0.0",
|
|
90
91
|
"@graphql-codegen/import-types-preset": "^3.0.1",
|
|
91
|
-
"@nuxt/kit": "4.0.
|
|
92
|
-
"@nuxt/schema": "4.0.
|
|
92
|
+
"@nuxt/kit": "^4.0.3",
|
|
93
|
+
"@nuxt/schema": "^4.0.3",
|
|
93
94
|
"@types/node": "^20.19.9",
|
|
94
95
|
"bumpp": "^10.2.0",
|
|
95
96
|
"changelogen": "^0.6.2",
|