nitro-graphql 2.0.0-beta.31 → 2.0.0-beta.33
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/dist/codegen/client-types.d.mts +13 -0
- package/dist/codegen/client-types.mjs +131 -0
- package/dist/codegen/external-types.d.mts +12 -0
- package/dist/codegen/external-types.mjs +88 -0
- package/dist/codegen/index.d.mts +18 -0
- package/dist/codegen/index.mjs +24 -0
- package/dist/codegen/server-types.d.mts +13 -0
- package/dist/codegen/server-types.mjs +64 -0
- package/dist/codegen/validation.d.mts +13 -0
- package/dist/codegen/validation.mjs +96 -0
- package/dist/config/defaults.mjs +36 -0
- package/dist/constants/scalars.mjs +27 -0
- package/dist/constants.mjs +106 -0
- package/dist/ecosystem/nuxt.mjs +3 -3
- package/dist/rollup.d.mts +2 -8
- package/dist/rollup.mjs +24 -199
- package/dist/routes/apollo-server.d.mts +2 -2
- package/dist/routes/apollo-server.mjs +7 -51
- package/dist/routes/debug-template.d.mts +15 -0
- package/dist/routes/debug-template.mjs +385 -0
- package/dist/routes/debug.d.mts +2 -2
- package/dist/routes/debug.mjs +1 -344
- package/dist/routes/graphql-yoga.d.mts +2 -2
- package/dist/routes/graphql-yoga.mjs +7 -51
- package/dist/routes/health.d.mts +2 -2
- package/dist/setup/file-watcher.mjs +84 -0
- package/dist/setup/graphql-scanner.mjs +25 -0
- package/dist/setup/rollup-integration.mjs +90 -0
- package/dist/setup/scaffold-generator.mjs +109 -0
- package/dist/setup/ts-config.mjs +69 -0
- package/dist/setup.d.mts +2 -2
- package/dist/setup.mjs +147 -303
- package/dist/types/index.d.mts +2 -2
- package/dist/utils/client-codegen.d.mts +1 -1
- package/dist/utils/client-codegen.mjs +5 -28
- package/dist/utils/codegen-plugin.d.mts +20 -0
- package/dist/utils/codegen-plugin.mjs +30 -0
- package/dist/utils/directive-parser.d.mts +2 -1
- package/dist/utils/directive-parser.mjs +4 -2
- package/dist/utils/federation.d.mts +29 -0
- package/dist/utils/federation.mjs +40 -0
- package/dist/utils/file-generator.mjs +1 -1
- package/dist/utils/file-writer.d.mts +35 -0
- package/dist/utils/file-writer.mjs +32 -0
- package/dist/utils/imports.d.mts +15 -0
- package/dist/utils/imports.mjs +25 -0
- package/dist/utils/index.d.mts +11 -38
- package/dist/utils/index.mjs +10 -287
- package/dist/utils/layers.d.mts +22 -0
- package/dist/utils/layers.mjs +28 -0
- package/dist/utils/ofetch-templates.d.mts +30 -0
- package/dist/utils/ofetch-templates.mjs +135 -0
- package/dist/utils/path-resolver.d.mts +2 -2
- package/dist/utils/path-resolver.mjs +2 -2
- package/dist/utils/scanning/common.d.mts +23 -0
- package/dist/utils/scanning/common.mjs +39 -0
- package/dist/utils/scanning/directives.d.mts +11 -0
- package/dist/utils/scanning/directives.mjs +43 -0
- package/dist/utils/scanning/documents.d.mts +15 -0
- package/dist/utils/scanning/documents.mjs +46 -0
- package/dist/utils/scanning/index.d.mts +6 -0
- package/dist/utils/scanning/index.mjs +7 -0
- package/dist/utils/scanning/resolvers.d.mts +11 -0
- package/dist/utils/scanning/resolvers.mjs +100 -0
- package/dist/utils/scanning/schemas.d.mts +15 -0
- package/dist/utils/scanning/schemas.mjs +29 -0
- package/dist/utils/schema-builder.d.mts +48 -0
- package/dist/utils/schema-builder.mjs +51 -0
- package/dist/utils/server-codegen.mjs +4 -27
- package/dist/utils/type-generation.d.mts +6 -12
- package/dist/utils/type-generation.mjs +6 -419
- package/dist/utils/validation.d.mts +11 -0
- package/dist/utils/validation.mjs +34 -0
- package/dist/virtual/generators/config.d.mts +22 -0
- package/dist/virtual/generators/config.mjs +36 -0
- package/dist/virtual/generators/debug.d.mts +14 -0
- package/dist/virtual/generators/debug.mjs +53 -0
- package/dist/virtual/generators/directives.d.mts +14 -0
- package/dist/virtual/generators/directives.mjs +52 -0
- package/dist/virtual/generators/index.d.mts +6 -0
- package/dist/virtual/generators/index.mjs +7 -0
- package/dist/virtual/generators/resolvers.d.mts +14 -0
- package/dist/virtual/generators/resolvers.mjs +55 -0
- package/dist/virtual/generators/schemas.d.mts +14 -0
- package/dist/virtual/generators/schemas.mjs +43 -0
- package/package.json +75 -57
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//#region src/utils/ofetch-templates.ts
|
|
2
|
+
/**
|
|
3
|
+
* Capitalize first letter of a string
|
|
4
|
+
*/
|
|
5
|
+
function capitalize(str) {
|
|
6
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generate ofetch client template content
|
|
10
|
+
*
|
|
11
|
+
* For main (default) service:
|
|
12
|
+
* - SDK exported as `$sdk`
|
|
13
|
+
* - Client function is `createGraphQLClient`
|
|
14
|
+
*
|
|
15
|
+
* For external services:
|
|
16
|
+
* - SDK exported as `$${serviceName}Sdk`
|
|
17
|
+
* - Client function is `create${ServiceName}GraphQLClient`
|
|
18
|
+
* - Endpoint has default value
|
|
19
|
+
*/
|
|
20
|
+
function generateOfetchTemplate(options) {
|
|
21
|
+
const { serviceName, isNuxt, endpoint, isExternal = false } = options;
|
|
22
|
+
if (isExternal) return generateExternalOfetchTemplate({
|
|
23
|
+
serviceName,
|
|
24
|
+
isNuxt,
|
|
25
|
+
endpoint
|
|
26
|
+
});
|
|
27
|
+
return generateMainOfetchTemplate({
|
|
28
|
+
isNuxt,
|
|
29
|
+
endpoint
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate ofetch template for main (default) GraphQL service
|
|
34
|
+
*/
|
|
35
|
+
function generateMainOfetchTemplate(options) {
|
|
36
|
+
const { isNuxt, endpoint } = options;
|
|
37
|
+
if (isNuxt) return `// This file is auto-generated once by nitro-graphql for quick start
|
|
38
|
+
// You can modify this file according to your needs
|
|
39
|
+
import type { Requester } from './sdk'
|
|
40
|
+
import { getSdk } from './sdk'
|
|
41
|
+
|
|
42
|
+
export function createGraphQLClient(endpoint: string): Requester {
|
|
43
|
+
return async <R>(doc: string, vars?: any): Promise<R> => {
|
|
44
|
+
const headers = import.meta.server ? useRequestHeaders() : undefined
|
|
45
|
+
|
|
46
|
+
const result = await $fetch(endpoint, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
body: { query: doc, variables: vars },
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
...headers,
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return result as R
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const $sdk = getSdk(createGraphQLClient('${endpoint}'))`;
|
|
60
|
+
return `// This file is auto-generated once by nitro-graphql for quick start
|
|
61
|
+
// You can modify this file according to your needs
|
|
62
|
+
import type { Requester } from './sdk'
|
|
63
|
+
import { ofetch } from 'ofetch'
|
|
64
|
+
import { getSdk } from './sdk'
|
|
65
|
+
|
|
66
|
+
export function createGraphQLClient(endpoint: string): Requester {
|
|
67
|
+
return async <R>(doc: string, vars?: any): Promise<R> => {
|
|
68
|
+
const result = await ofetch(endpoint, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
body: { query: doc, variables: vars },
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'application/json',
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return result as R
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const $sdk = getSdk(createGraphQLClient('${endpoint}'))`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate ofetch template for external GraphQL service
|
|
84
|
+
*/
|
|
85
|
+
function generateExternalOfetchTemplate(options) {
|
|
86
|
+
const { serviceName, isNuxt, endpoint } = options;
|
|
87
|
+
const capitalizedName = capitalize(serviceName);
|
|
88
|
+
if (isNuxt) return `// This file is auto-generated once by nitro-graphql for quick start
|
|
89
|
+
// You can modify this file according to your needs
|
|
90
|
+
import type { Sdk, Requester } from './sdk'
|
|
91
|
+
import { getSdk } from './sdk'
|
|
92
|
+
|
|
93
|
+
export function create${capitalizedName}GraphQLClient(endpoint: string = '${endpoint}'): Requester {
|
|
94
|
+
return async <R>(doc: string, vars?: any): Promise<R> => {
|
|
95
|
+
const headers = import.meta.server ? useRequestHeaders() : undefined
|
|
96
|
+
|
|
97
|
+
const result = await $fetch(endpoint, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
body: { query: doc, variables: vars },
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
...headers,
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return result as R
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const $${serviceName}Sdk: Sdk = getSdk(create${capitalizedName}GraphQLClient())`;
|
|
111
|
+
return `// This file is auto-generated once by nitro-graphql for quick start
|
|
112
|
+
// You can modify this file according to your needs
|
|
113
|
+
import type { Sdk, Requester } from './sdk'
|
|
114
|
+
import { ofetch } from 'ofetch'
|
|
115
|
+
import { getSdk } from './sdk'
|
|
116
|
+
|
|
117
|
+
export function create${capitalizedName}GraphQLClient(endpoint: string = '${endpoint}'): Requester {
|
|
118
|
+
return async <R>(doc: string, vars?: any): Promise<R> => {
|
|
119
|
+
const result = await ofetch(endpoint, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
body: { query: doc, variables: vars },
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
return result as R
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const $${serviceName}Sdk: Sdk = getSdk(create${capitalizedName}GraphQLClient())`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
export { generateOfetchTemplate };
|
|
@@ -53,7 +53,7 @@ declare function getClientUtilsConfig(nitro: Nitro): ClientUtilsConfig;
|
|
|
53
53
|
/**
|
|
54
54
|
* Check if SDK files should be generated (category-level check)
|
|
55
55
|
*/
|
|
56
|
-
declare function
|
|
56
|
+
declare function shouldGenerateSdk(nitro: Nitro): boolean;
|
|
57
57
|
/**
|
|
58
58
|
* Get SDK configuration (handles false case)
|
|
59
59
|
*/
|
|
@@ -67,4 +67,4 @@ declare function shouldGenerateTypes(nitro: Nitro): boolean;
|
|
|
67
67
|
*/
|
|
68
68
|
declare function getTypesConfig(nitro: Nitro): TypesConfig;
|
|
69
69
|
//#endregion
|
|
70
|
-
export { PathPlaceholders, getClientUtilsConfig, getDefaultPaths, getScaffoldConfig, getSdkConfig, getTypesConfig, replacePlaceholders, resolveFilePath, shouldGenerateClientUtils, shouldGenerateFile,
|
|
70
|
+
export { PathPlaceholders, getClientUtilsConfig, getDefaultPaths, getScaffoldConfig, getSdkConfig, getTypesConfig, replacePlaceholders, resolveFilePath, shouldGenerateClientUtils, shouldGenerateFile, shouldGenerateScaffold, shouldGenerateSdk, shouldGenerateTypes };
|
|
@@ -91,7 +91,7 @@ function getClientUtilsConfig(nitro) {
|
|
|
91
91
|
/**
|
|
92
92
|
* Check if SDK files should be generated (category-level check)
|
|
93
93
|
*/
|
|
94
|
-
function
|
|
94
|
+
function shouldGenerateSdk(nitro) {
|
|
95
95
|
const sdkConfig = nitro.options.graphql?.sdk;
|
|
96
96
|
if (sdkConfig === false) return false;
|
|
97
97
|
if (sdkConfig && sdkConfig.enabled === false) return false;
|
|
@@ -124,4 +124,4 @@ function getTypesConfig(nitro) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
//#endregion
|
|
127
|
-
export { getClientUtilsConfig, getDefaultPaths, getScaffoldConfig, getSdkConfig, getTypesConfig, replacePlaceholders, resolveFilePath, shouldGenerateClientUtils, shouldGenerateFile,
|
|
127
|
+
export { getClientUtilsConfig, getDefaultPaths, getScaffoldConfig, getSdkConfig, getTypesConfig, replacePlaceholders, resolveFilePath, shouldGenerateClientUtils, shouldGenerateFile, shouldGenerateScaffold, shouldGenerateSdk, shouldGenerateTypes };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Nitro } from "nitro/types";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/scanning/common.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* File information returned from scanning operations
|
|
7
|
+
*/
|
|
8
|
+
interface FileInfo {
|
|
9
|
+
path: string;
|
|
10
|
+
fullPath: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Scan a directory for files matching a glob pattern
|
|
14
|
+
*/
|
|
15
|
+
declare function scanDir(nitro: Nitro, dir: string, name: string, globPattern?: string): Promise<FileInfo[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Deduplicate files by fullPath
|
|
18
|
+
*/
|
|
19
|
+
declare function deduplicateFiles<T extends {
|
|
20
|
+
fullPath: string;
|
|
21
|
+
}>(files: T[]): T[];
|
|
22
|
+
//#endregion
|
|
23
|
+
export { FileInfo, deduplicateFiles, scanDir };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { GLOB_SCAN_PATTERN } from "../../constants.mjs";
|
|
2
|
+
import { join, relative } from "pathe";
|
|
3
|
+
import { glob } from "tinyglobby";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/scanning/common.ts
|
|
6
|
+
/**
|
|
7
|
+
* Scan a directory for files matching a glob pattern
|
|
8
|
+
*/
|
|
9
|
+
async function scanDir(nitro, dir, name, globPattern = GLOB_SCAN_PATTERN) {
|
|
10
|
+
return (await glob(join(name, globPattern), {
|
|
11
|
+
cwd: dir,
|
|
12
|
+
dot: true,
|
|
13
|
+
ignore: nitro.options.ignore,
|
|
14
|
+
absolute: true
|
|
15
|
+
}).catch((error) => {
|
|
16
|
+
if (error?.code === "ENOTDIR") {
|
|
17
|
+
nitro.logger.warn(`Ignoring \`${join(dir, name)}\`. It must be a directory.`);
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
})).map((fullPath) => ({
|
|
22
|
+
fullPath,
|
|
23
|
+
path: relative(join(dir, name), fullPath)
|
|
24
|
+
})).sort((a, b) => a.path.localeCompare(b.path));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Deduplicate files by fullPath
|
|
28
|
+
*/
|
|
29
|
+
function deduplicateFiles(files) {
|
|
30
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
31
|
+
return files.filter((file) => {
|
|
32
|
+
if (seenPaths.has(file.fullPath)) return false;
|
|
33
|
+
seenPaths.add(file.fullPath);
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { deduplicateFiles, scanDir };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GenImport } from "../../types/index.mjs";
|
|
2
|
+
import { Nitro } from "nitro/types";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/scanning/directives.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Scan for directive files and parse their exports
|
|
8
|
+
*/
|
|
9
|
+
declare function scanDirectives(nitro: Nitro): Promise<GenImport[]>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { scanDirectives };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getLayerServerDirectories } from "../layers.mjs";
|
|
2
|
+
import { deduplicateFiles, scanDir } from "./common.mjs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { relative } from "pathe";
|
|
5
|
+
import { hash } from "ohash";
|
|
6
|
+
import { parseSync } from "oxc-parser";
|
|
7
|
+
|
|
8
|
+
//#region src/utils/scanning/directives.ts
|
|
9
|
+
/**
|
|
10
|
+
* Scan for directive files and parse their exports
|
|
11
|
+
*/
|
|
12
|
+
async function scanDirectives(nitro) {
|
|
13
|
+
const serverDirRelative = relative(nitro.options.rootDir, nitro.graphql.serverDir);
|
|
14
|
+
const regularFiles = await scanDir(nitro, nitro.options.rootDir, serverDirRelative, "**/*.directive.{ts,js}");
|
|
15
|
+
const layerServerDirs = getLayerServerDirectories(nitro);
|
|
16
|
+
const layerFiles = await Promise.all(layerServerDirs.map((layerServerDir) => scanDir(nitro, layerServerDir, "graphql", "**/*.directive.{ts,js}"))).then((r) => r.flat());
|
|
17
|
+
const files = deduplicateFiles([...regularFiles, ...layerFiles]);
|
|
18
|
+
const exportName = [];
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
const fileContent = await readFile(file.fullPath, "utf-8");
|
|
21
|
+
const parsed = parseSync(file.fullPath, fileContent);
|
|
22
|
+
const exports = {
|
|
23
|
+
imports: [],
|
|
24
|
+
specifier: file.fullPath
|
|
25
|
+
};
|
|
26
|
+
for (const node of parsed.program.body) if (node.type === "ExportNamedDeclaration" && node.declaration && node.declaration.type === "VariableDeclaration") {
|
|
27
|
+
for (const decl of node.declaration.declarations) if (decl.type === "VariableDeclarator" && decl.init && decl.id.type === "Identifier") {
|
|
28
|
+
if (decl.init && decl.init.type === "CallExpression") {
|
|
29
|
+
if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineDirective") exports.imports.push({
|
|
30
|
+
name: decl.id.name,
|
|
31
|
+
type: "directive",
|
|
32
|
+
as: `_${hash(decl.id.name + file.fullPath).replace(/-/g, "").slice(0, 6)}`
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (exports.imports.length > 0) exportName.push(exports);
|
|
38
|
+
}
|
|
39
|
+
return exportName;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
export { scanDirectives };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Nitro } from "nitro/types";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/scanning/documents.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scan for GraphQL client documents (.graphql) in client directory
|
|
7
|
+
* Excludes files from external service directories
|
|
8
|
+
*/
|
|
9
|
+
declare function scanDocuments(nitro: Nitro): Promise<string[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Scan documents for a specific external service
|
|
12
|
+
*/
|
|
13
|
+
declare function scanExternalServiceDocs(nitro: Nitro, serviceName: string, patterns: string[]): Promise<string[]>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { scanDocuments, scanExternalServiceDocs };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getLayerAppDirectories } from "../layers.mjs";
|
|
2
|
+
import { deduplicateFiles, scanDir } from "./common.mjs";
|
|
3
|
+
import { glob } from "tinyglobby";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/scanning/documents.ts
|
|
6
|
+
/**
|
|
7
|
+
* Scan for GraphQL client documents (.graphql) in client directory
|
|
8
|
+
* Excludes files from external service directories
|
|
9
|
+
*/
|
|
10
|
+
async function scanDocuments(nitro) {
|
|
11
|
+
const files = await scanDir(nitro, nitro.options.rootDir, nitro.graphql.dir.client, "**/*.graphql");
|
|
12
|
+
const layerAppDirs = getLayerAppDirectories(nitro);
|
|
13
|
+
const layerFiles = await Promise.all(layerAppDirs.map((layerAppDir) => scanDir(nitro, layerAppDir, "graphql", "**/*.graphql"))).then((r) => r.flat());
|
|
14
|
+
const allFiles = deduplicateFiles([...files, ...layerFiles]);
|
|
15
|
+
const externalPatterns = (nitro.options.graphql?.externalServices || []).flatMap((service) => service.documents || []);
|
|
16
|
+
return allFiles.filter((f) => !f.path.startsWith("external/")).filter((f) => {
|
|
17
|
+
const relativePath = f.path;
|
|
18
|
+
for (const pattern of externalPatterns) {
|
|
19
|
+
const clientDirPattern = `${nitro.graphql.dir.client}/`;
|
|
20
|
+
if (pattern.replace(/* @__PURE__ */ new RegExp(`^${clientDirPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "").split("/")[0] === relativePath.split("/")[0]) return false;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}).map((f) => f.fullPath);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Scan documents for a specific external service
|
|
27
|
+
*/
|
|
28
|
+
async function scanExternalServiceDocs(nitro, serviceName, patterns) {
|
|
29
|
+
if (!patterns.length) return [];
|
|
30
|
+
const files = [];
|
|
31
|
+
for (const pattern of patterns) try {
|
|
32
|
+
const serviceFiles = await glob(pattern, {
|
|
33
|
+
cwd: nitro.options.rootDir,
|
|
34
|
+
dot: true,
|
|
35
|
+
ignore: nitro.options.ignore,
|
|
36
|
+
absolute: true
|
|
37
|
+
});
|
|
38
|
+
files.push(...serviceFiles);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
nitro.logger.warn(`[graphql:${serviceName}] Error scanning documents with pattern "${pattern}":`, error);
|
|
41
|
+
}
|
|
42
|
+
return files.filter((file, index, self) => self.indexOf(file) === index);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { scanDocuments, scanExternalServiceDocs };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { FileInfo, deduplicateFiles, scanDir } from "./common.mjs";
|
|
2
|
+
import { scanDirectives } from "./directives.mjs";
|
|
3
|
+
import { scanDocuments, scanExternalServiceDocs } from "./documents.mjs";
|
|
4
|
+
import { scanResolvers } from "./resolvers.mjs";
|
|
5
|
+
import { scanGraphql, scanSchemas } from "./schemas.mjs";
|
|
6
|
+
export { type FileInfo, deduplicateFiles, scanDir, scanDirectives, scanDocuments, scanExternalServiceDocs, scanGraphql, scanResolvers, scanSchemas };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { deduplicateFiles, scanDir } from "./common.mjs";
|
|
2
|
+
import { scanDirectives } from "./directives.mjs";
|
|
3
|
+
import { scanDocuments, scanExternalServiceDocs } from "./documents.mjs";
|
|
4
|
+
import { scanResolvers } from "./resolvers.mjs";
|
|
5
|
+
import { scanGraphql, scanSchemas } from "./schemas.mjs";
|
|
6
|
+
|
|
7
|
+
export { deduplicateFiles, scanDir, scanDirectives, scanDocuments, scanExternalServiceDocs, scanGraphql, scanResolvers, scanSchemas };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GenImport } from "../../types/index.mjs";
|
|
2
|
+
import { Nitro } from "nitro/types";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/scanning/resolvers.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Scan for resolver files and parse their exports
|
|
8
|
+
*/
|
|
9
|
+
declare function scanResolvers(nitro: Nitro): Promise<GenImport[]>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { scanResolvers };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { DEFINE_FUNCTIONS } from "../../constants.mjs";
|
|
2
|
+
import { getLayerServerDirectories } from "../layers.mjs";
|
|
3
|
+
import { deduplicateFiles, scanDir } from "./common.mjs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { basename, relative } from "pathe";
|
|
6
|
+
import { hash } from "ohash";
|
|
7
|
+
import { parseSync } from "oxc-parser";
|
|
8
|
+
|
|
9
|
+
//#region src/utils/scanning/resolvers.ts
|
|
10
|
+
/**
|
|
11
|
+
* Scan for resolver files and parse their exports
|
|
12
|
+
*/
|
|
13
|
+
async function scanResolvers(nitro) {
|
|
14
|
+
const serverDirRelative = relative(nitro.options.rootDir, nitro.graphql.serverDir);
|
|
15
|
+
const regularFiles = await scanDir(nitro, nitro.options.rootDir, serverDirRelative, "**/*.resolver.{ts,js}");
|
|
16
|
+
const layerServerDirs = getLayerServerDirectories(nitro);
|
|
17
|
+
const layerFiles = await Promise.all(layerServerDirs.map((layerServerDir) => scanDir(nitro, layerServerDir, "graphql", "**/*.resolver.{ts,js}"))).then((r) => r.flat());
|
|
18
|
+
const files = deduplicateFiles([...regularFiles, ...layerFiles]);
|
|
19
|
+
const exportName = [];
|
|
20
|
+
for (const file of files) try {
|
|
21
|
+
const fileContent = await readFile(file.fullPath, "utf-8");
|
|
22
|
+
const parsed = parseSync(file.fullPath, fileContent);
|
|
23
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
24
|
+
if (nitro.options.dev) {
|
|
25
|
+
const fileName = basename(file.fullPath);
|
|
26
|
+
const firstError = parsed.errors[0];
|
|
27
|
+
const location = firstError?.labels?.[0];
|
|
28
|
+
const lineInfo = location ? `:${location.start}` : "";
|
|
29
|
+
const message = firstError?.message.split(",")[0] || "Syntax error";
|
|
30
|
+
console.error(`✖ ${fileName}${lineInfo} - ${message}`);
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const exports = {
|
|
35
|
+
imports: [],
|
|
36
|
+
specifier: file.fullPath
|
|
37
|
+
};
|
|
38
|
+
let hasDefaultExport = false;
|
|
39
|
+
let hasNamedExport = false;
|
|
40
|
+
const namedExports = [];
|
|
41
|
+
for (const node of parsed.program.body) {
|
|
42
|
+
if (node.type === "ExportDefaultDeclaration") hasDefaultExport = true;
|
|
43
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration && node.declaration.type === "VariableDeclaration") {
|
|
44
|
+
for (const decl of node.declaration.declarations) if (decl.type === "VariableDeclarator" && decl.init && decl.id.type === "Identifier") {
|
|
45
|
+
hasNamedExport = true;
|
|
46
|
+
namedExports.push(decl.id.name);
|
|
47
|
+
if (decl.init && decl.init.type === "CallExpression") {
|
|
48
|
+
if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineResolver") exports.imports.push({
|
|
49
|
+
name: decl.id.name,
|
|
50
|
+
type: "resolver",
|
|
51
|
+
as: `_${hash(decl.id.name + file.fullPath).replace(/-/g, "").slice(0, 6)}`
|
|
52
|
+
});
|
|
53
|
+
if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineQuery") exports.imports.push({
|
|
54
|
+
name: decl.id.name,
|
|
55
|
+
type: "query",
|
|
56
|
+
as: `_${hash(decl.id.name + file.fullPath).replace(/-/g, "").slice(0, 6)}`
|
|
57
|
+
});
|
|
58
|
+
if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineMutation") exports.imports.push({
|
|
59
|
+
name: decl.id.name,
|
|
60
|
+
type: "mutation",
|
|
61
|
+
as: `_${hash(decl.id.name + file.fullPath).replace(/-/g, "").slice(0, 6)}`
|
|
62
|
+
});
|
|
63
|
+
if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineField") exports.imports.push({
|
|
64
|
+
name: decl.id.name,
|
|
65
|
+
type: "type",
|
|
66
|
+
as: `_${hash(decl.id.name + file.fullPath).replace(/-/g, "").slice(0, 6)}`
|
|
67
|
+
});
|
|
68
|
+
if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineSubscription") exports.imports.push({
|
|
69
|
+
name: decl.id.name,
|
|
70
|
+
type: "subscription",
|
|
71
|
+
as: `_${hash(decl.id.name + file.fullPath).replace(/-/g, "").slice(0, 6)}`
|
|
72
|
+
});
|
|
73
|
+
if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineDirective") exports.imports.push({
|
|
74
|
+
name: decl.id.name,
|
|
75
|
+
type: "directive",
|
|
76
|
+
as: `_${hash(decl.id.name + file.fullPath).replace(/-/g, "").slice(0, 6)}`
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (nitro.options.dev) {
|
|
83
|
+
const relPath = relative(nitro.options.rootDir, file.fullPath);
|
|
84
|
+
if (hasDefaultExport && !hasNamedExport) nitro.logger.warn(`[nitro-graphql] ${relPath}: Using default export instead of named export. Resolvers must use named exports like "export const myResolver = defineQuery(...)". Default exports are not detected.`);
|
|
85
|
+
if (exports.imports.length === 0 && hasNamedExport) {
|
|
86
|
+
const validFunctions = DEFINE_FUNCTIONS.join(", ");
|
|
87
|
+
nitro.logger.warn(`[nitro-graphql] ${relPath}: File has named exports [${namedExports.join(", ")}] but none use the required define functions (${validFunctions}). Exports will not be registered.`);
|
|
88
|
+
}
|
|
89
|
+
if (!hasDefaultExport && !hasNamedExport) nitro.logger.warn(`[nitro-graphql] ${relPath}: No exports found. Resolver files must export resolvers using defineResolver, defineQuery, defineMutation, etc.`);
|
|
90
|
+
}
|
|
91
|
+
if (exports.imports.length > 0) exportName.push(exports);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const relPath = relative(nitro.options.rootDir, file.fullPath);
|
|
94
|
+
nitro.logger.error(`[nitro-graphql] Failed to parse resolver file ${relPath}:`, error);
|
|
95
|
+
}
|
|
96
|
+
return exportName;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
export { scanResolvers };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Nitro } from "nitro/types";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/scanning/schemas.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scan for GraphQL schema files (.graphql) in server directory
|
|
7
|
+
*/
|
|
8
|
+
declare function scanSchemas(nitro: Nitro): Promise<string[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Scan for GraphQL files (.graphql, .gql) in server directory
|
|
11
|
+
* Similar to scanSchemas but includes .gql extension
|
|
12
|
+
*/
|
|
13
|
+
declare function scanGraphql(nitro: Nitro): Promise<string[]>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { scanGraphql, scanSchemas };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getLayerServerDirectories } from "../layers.mjs";
|
|
2
|
+
import { deduplicateFiles, scanDir } from "./common.mjs";
|
|
3
|
+
import { relative } from "pathe";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/scanning/schemas.ts
|
|
6
|
+
/**
|
|
7
|
+
* Scan for GraphQL schema files (.graphql) in server directory
|
|
8
|
+
*/
|
|
9
|
+
async function scanSchemas(nitro) {
|
|
10
|
+
const serverDirRelative = relative(nitro.options.rootDir, nitro.graphql.serverDir);
|
|
11
|
+
const files = await scanDir(nitro, nitro.options.rootDir, serverDirRelative, "**/*.graphql");
|
|
12
|
+
const layerServerDirs = getLayerServerDirectories(nitro);
|
|
13
|
+
const layerFiles = await Promise.all(layerServerDirs.map((layerServerDir) => scanDir(nitro, layerServerDir, "graphql", "**/*.graphql"))).then((r) => r.flat());
|
|
14
|
+
return deduplicateFiles([...files, ...layerFiles]).map((f) => f.fullPath);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Scan for GraphQL files (.graphql, .gql) in server directory
|
|
18
|
+
* Similar to scanSchemas but includes .gql extension
|
|
19
|
+
*/
|
|
20
|
+
async function scanGraphql(nitro) {
|
|
21
|
+
const serverDirRelative = relative(nitro.options.rootDir, nitro.graphql.serverDir);
|
|
22
|
+
const files = await scanDir(nitro, nitro.options.rootDir, serverDirRelative, "**/*.{graphql,gql}");
|
|
23
|
+
const layerServerDirs = getLayerServerDirectories(nitro);
|
|
24
|
+
const layerFiles = await Promise.all(layerServerDirs.map((layerServerDir) => scanDir(nitro, layerServerDir, "graphql", "**/*.{graphql,gql}"))).then((r) => r.flat());
|
|
25
|
+
return deduplicateFiles([...files, ...layerFiles]).map((f) => f.fullPath);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { scanGraphql, scanSchemas };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DirectiveDefinition } from "../types/define.mjs";
|
|
2
|
+
import { GraphQLSchema } from "graphql";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/schema-builder.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Schema definition from virtual module
|
|
8
|
+
*/
|
|
9
|
+
interface SchemaDefinition {
|
|
10
|
+
def: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolver definition from virtual module
|
|
14
|
+
*/
|
|
15
|
+
interface ResolverDefinition {
|
|
16
|
+
resolver: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Directive wrapper from virtual module
|
|
20
|
+
*/
|
|
21
|
+
interface DirectiveWrapper {
|
|
22
|
+
directive: DirectiveDefinition;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Module configuration for federation
|
|
26
|
+
*/
|
|
27
|
+
interface ModuleConfig {
|
|
28
|
+
federation?: {
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
serviceName?: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Options for creating a merged schema
|
|
35
|
+
*/
|
|
36
|
+
interface CreateMergedSchemaOptions {
|
|
37
|
+
schemas: SchemaDefinition[];
|
|
38
|
+
resolvers: ResolverDefinition[];
|
|
39
|
+
directives?: DirectiveWrapper[];
|
|
40
|
+
moduleConfig: ModuleConfig;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a merged GraphQL schema from schemas, resolvers, and directives
|
|
44
|
+
* Supports Apollo Federation when enabled
|
|
45
|
+
*/
|
|
46
|
+
declare function createMergedSchema(options: CreateMergedSchemaOptions): Promise<GraphQLSchema>;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { CreateMergedSchemaOptions, DirectiveWrapper, ModuleConfig, ResolverDefinition, SchemaDefinition, createMergedSchema };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { loadFederationSupport, warnFederationUnavailable } from "./federation.mjs";
|
|
2
|
+
import { consola as consola$1 } from "consola";
|
|
3
|
+
import { parse } from "graphql";
|
|
4
|
+
import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge";
|
|
5
|
+
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
6
|
+
|
|
7
|
+
//#region src/utils/schema-builder.ts
|
|
8
|
+
/**
|
|
9
|
+
* Create a merged GraphQL schema from schemas, resolvers, and directives
|
|
10
|
+
* Supports Apollo Federation when enabled
|
|
11
|
+
*/
|
|
12
|
+
async function createMergedSchema(options) {
|
|
13
|
+
const { schemas, resolvers, directives, moduleConfig } = options;
|
|
14
|
+
try {
|
|
15
|
+
const typeDefs = mergeTypeDefs([schemas.map((schema$1) => schema$1.def).join("\n\n")], {
|
|
16
|
+
throwOnConflict: true,
|
|
17
|
+
commentDescriptions: true,
|
|
18
|
+
sort: true
|
|
19
|
+
});
|
|
20
|
+
const mergedResolvers = mergeResolvers(resolvers.map((r) => r.resolver));
|
|
21
|
+
const federationEnabled = moduleConfig.federation?.enabled;
|
|
22
|
+
let schema;
|
|
23
|
+
if (federationEnabled) {
|
|
24
|
+
const buildSubgraph = await loadFederationSupport();
|
|
25
|
+
if (buildSubgraph) schema = buildSubgraph({
|
|
26
|
+
typeDefs: typeof typeDefs === "string" ? parse(typeDefs) : typeDefs,
|
|
27
|
+
resolvers: mergedResolvers
|
|
28
|
+
});
|
|
29
|
+
else {
|
|
30
|
+
warnFederationUnavailable();
|
|
31
|
+
schema = makeExecutableSchema({
|
|
32
|
+
typeDefs,
|
|
33
|
+
resolvers: mergedResolvers
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
} else schema = makeExecutableSchema({
|
|
37
|
+
typeDefs,
|
|
38
|
+
resolvers: mergedResolvers
|
|
39
|
+
});
|
|
40
|
+
if (directives && directives.length > 0) {
|
|
41
|
+
for (const { directive } of directives) if (directive.transformer) schema = directive.transformer(schema);
|
|
42
|
+
}
|
|
43
|
+
return schema;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
consola$1.error("Schema merge error:", error);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
export { createMergedSchema };
|
|
@@ -1,40 +1,17 @@
|
|
|
1
|
+
import { DEFAULT_GRAPHQL_SCALARS } from "../constants/scalars.mjs";
|
|
2
|
+
import { pluginContent } from "./codegen-plugin.mjs";
|
|
1
3
|
import { defu as defu$1 } from "defu";
|
|
2
4
|
import consola from "consola";
|
|
3
5
|
import { parse } from "graphql";
|
|
4
|
-
import { printSchemaWithDirectives } from "@graphql-tools/utils";
|
|
5
6
|
import { codegen } from "@graphql-codegen/core";
|
|
6
7
|
import * as typescriptPlugin from "@graphql-codegen/typescript";
|
|
7
|
-
import {
|
|
8
|
+
import { printSchemaWithDirectives } from "@graphql-tools/utils";
|
|
8
9
|
import * as typescriptResolversPlugin from "@graphql-codegen/typescript-resolvers";
|
|
9
10
|
|
|
10
11
|
//#region src/utils/server-codegen.ts
|
|
11
|
-
function pluginContent(_schema, _documents, _config, _info) {
|
|
12
|
-
return {
|
|
13
|
-
prepend: [
|
|
14
|
-
"// THIS FILE IS GENERATED, DO NOT EDIT!",
|
|
15
|
-
"/* eslint-disable eslint-comments/no-unlimited-disable */",
|
|
16
|
-
"/* tslint:disable */",
|
|
17
|
-
"/* eslint-disable */",
|
|
18
|
-
"/* prettier-ignore */"
|
|
19
|
-
],
|
|
20
|
-
content: ""
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
12
|
async function generateTypes(selectFremework, schema, config = {}, outputPath) {
|
|
24
13
|
const mergedConfig = defu$1({
|
|
25
|
-
scalars:
|
|
26
|
-
DateTime: DateTimeResolver.extensions.codegenScalarType,
|
|
27
|
-
DateTimeISO: DateTimeISOResolver.extensions.codegenScalarType,
|
|
28
|
-
UUID: UUIDResolver.extensions.codegenScalarType,
|
|
29
|
-
JSON: JSONResolver.extensions.codegenScalarType,
|
|
30
|
-
JSONObject: JSONObjectResolver.extensions.codegenScalarType,
|
|
31
|
-
NonEmptyString: NonEmptyStringResolver.extensions.codegenScalarType,
|
|
32
|
-
Currency: CurrencyResolver.extensions.codegenScalarType,
|
|
33
|
-
File: {
|
|
34
|
-
input: "File",
|
|
35
|
-
output: "File"
|
|
36
|
-
}
|
|
37
|
-
},
|
|
14
|
+
scalars: DEFAULT_GRAPHQL_SCALARS,
|
|
38
15
|
defaultScalarType: "unknown",
|
|
39
16
|
defaultMapper: `ResolverReturnType<{T}>`,
|
|
40
17
|
contextType: "nitro/h3#H3Event",
|