nitro-graphql 1.2.1 → 1.2.3

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,7 +30,6 @@
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
33
  - 🔗 **Multi-Service Support**: Connect to multiple external GraphQL APIs alongside your main server
35
34
 
36
35
  ## 🎯 Used Projects
@@ -196,10 +195,6 @@ server/
196
195
  ├── graphql/
197
196
  │ ├── schema.graphql # Main schema with scalars and base types
198
197
  │ ├── 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
203
198
  │ ├── users/
204
199
  │ │ ├── user.graphql # User schema definitions
205
200
  │ │ ├── user-queries.resolver.ts # User query resolvers (use named exports)
@@ -634,90 +629,6 @@ export const postTypes = defineType({
634
629
 
635
630
  </details>
636
631
 
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
-
721
632
  <details>
722
633
  <summary><strong>defineSchema</strong> - Define custom schema with validation</summary>
723
634
 
@@ -844,75 +755,6 @@ export default defineNitroConfig({
844
755
 
845
756
  ## 🔥 Advanced Features
846
757
 
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
-
916
758
  <details>
917
759
  <summary><strong>Custom Scalars</strong></summary>
918
760
 
@@ -1,7 +1,7 @@
1
- import * as _nuxt_schema1 from "@nuxt/schema";
1
+ import * as _nuxt_schema0 from "@nuxt/schema";
2
2
 
3
3
  //#region src/ecosystem/nuxt.d.ts
4
4
  interface ModuleOptions {}
5
- declare const _default: _nuxt_schema1.NuxtModule<ModuleOptions, ModuleOptions, false>;
5
+ declare const _default: _nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
6
6
  //#endregion
7
7
  export { ModuleOptions, _default as default };
@@ -1,3 +1,4 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
2
  import { join, resolve } from "pathe";
2
3
  import { defineNuxtModule } from "@nuxt/kit";
3
4
 
@@ -31,6 +32,27 @@ var nuxt_default = defineNuxtModule({
31
32
  nuxt.hook("imports:dirs", (dirs) => {
32
33
  dirs.push(resolve(nuxt.options.srcDir, "graphql"));
33
34
  });
35
+ nuxt.hook("nitro:config", () => {
36
+ const clientDir = join(nuxt.options.buildDir, "graphql");
37
+ const appGraphqlDir = resolve(nuxt.options.rootDir, "app/graphql");
38
+ const hasAppGraphqlDir = existsSync(appGraphqlDir);
39
+ if (!hasAppGraphqlDir) {
40
+ const defaultDir = join(clientDir, "default");
41
+ if (!existsSync(defaultDir)) mkdirSync(defaultDir, { recursive: true });
42
+ const sampleQueryFile = join(defaultDir, "queries.graphql");
43
+ if (!existsSync(sampleQueryFile)) writeFileSync(sampleQueryFile, `# Example GraphQL queries
44
+ # Add your GraphQL queries here
45
+
46
+ # query GetUser($id: ID!) {
47
+ # user(id: $id) {
48
+ # id
49
+ # name
50
+ # email
51
+ # }
52
+ # }
53
+ `, "utf-8");
54
+ }
55
+ });
34
56
  }
35
57
  });
36
58
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { StandardSchemaV1 } from "./types/standard-schema.js";
2
2
  import { CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, GenImport, GenericSdkConfig, NitroGraphQLOptions } from "./types/index.js";
3
- import * as nitropack0 from "nitropack";
3
+ import * as nitropack5 from "nitropack";
4
4
 
5
5
  //#region src/index.d.ts
6
- declare const _default: nitropack0.NitroModule;
6
+ declare const _default: nitropack5.NitroModule;
7
7
  //#endregion
8
8
  export { CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, GenImport, GenericSdkConfig, NitroGraphQLOptions, StandardSchemaV1, _default as default };
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
- import { generateDirectiveSchemas } from "./utils/directive-parser.js";
2
- import { relativeWithDot, scanDirectives, scanDocs, scanResolvers, scanSchemas, validateExternalServices } from "./utils/index.js";
1
+ import { relativeWithDot, scanDocs, scanResolvers, scanSchemas, validateExternalServices } from "./utils/index.js";
3
2
  import { clientTypeGeneration, serverTypeGeneration } from "./utils/type-generation.js";
4
3
  import { rollupConfig } from "./rollup.js";
5
4
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
@@ -80,7 +79,7 @@ var src_default = defineNitroModule({
80
79
  const watcher = watch(watchDirs, {
81
80
  persistent: true,
82
81
  ignoreInitial: true,
83
- ignored: [...nitro.options.ignore, "**/server/graphql/_directives.graphql"]
82
+ ignored: nitro.options.ignore
84
83
  }).on("all", async (event, path) => {
85
84
  if (path.endsWith(".graphql") || path.endsWith(".gql")) await clientTypeGeneration(nitro);
86
85
  });
@@ -96,17 +95,11 @@ var src_default = defineNitroModule({
96
95
  nitro.scanDocuments = docs;
97
96
  const resolvers = await scanResolvers(nitro);
98
97
  nitro.scanResolvers = resolvers;
99
- const directives = await scanDirectives(nitro);
100
- nitro.scanDirectives = directives;
101
- await generateDirectiveSchemas(nitro, directives);
102
98
  nitro.hooks.hook("dev:start", async () => {
103
99
  const schemas$1 = await scanSchemas(nitro);
104
100
  nitro.scanSchemas = schemas$1;
105
101
  const resolvers$1 = await scanResolvers(nitro);
106
102
  nitro.scanResolvers = resolvers$1;
107
- const directives$1 = await scanDirectives(nitro);
108
- nitro.scanDirectives = directives$1;
109
- await generateDirectiveSchemas(nitro, directives$1);
110
103
  const docs$1 = await scanDocs(nitro);
111
104
  nitro.scanDocuments = docs$1;
112
105
  });
@@ -149,8 +142,7 @@ var src_default = defineNitroModule({
149
142
  "defineSubscription",
150
143
  "defineType",
151
144
  "defineGraphQLConfig",
152
- "defineSchema",
153
- "defineDirective"
145
+ "defineSchema"
154
146
  ]
155
147
  });
156
148
  }
@@ -246,23 +238,6 @@ declare module 'h3' {
246
238
  consola.warn("nitro-graphql: Found context.d.ts file. Please rename it to context.ts for the new structure.");
247
239
  consola.info("The context file should now be context.ts instead of context.d.ts");
248
240
  }
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
- }
266
241
  }
267
242
  });
268
243
 
package/dist/rollup.js CHANGED
@@ -9,7 +9,6 @@ import { genImport } from "knitwork";
9
9
  async function rollupConfig(app) {
10
10
  virtualSchemas(app);
11
11
  virtualResolvers(app);
12
- virtualDirectives(app);
13
12
  getGraphQLConfig(app);
14
13
  app.hooks.hook("rollup:before", (nitro, rollupConfig$1) => {
15
14
  rollupConfig$1.plugins = rollupConfig$1.plugins || [];
@@ -86,29 +85,6 @@ function virtualResolvers(app) {
86
85
  return code;
87
86
  };
88
87
  }
89
- function virtualDirectives(app) {
90
- const getDirectives = () => {
91
- const directives = [...app.scanDirectives || []];
92
- return directives;
93
- };
94
- app.options.virtual ??= {};
95
- app.options.virtual["#nitro-internal-virtual/server-directives"] = () => {
96
- const imports = getDirectives();
97
- const importsContent = [...imports.map(({ specifier, imports: imports$1, options }) => {
98
- return genImport(specifier, imports$1, options);
99
- })];
100
- const data = imports.map(({ imports: imports$1 }) => imports$1.map((i) => `{ directive: ${i.as} }`).join(",\n")).filter(Boolean).join(",\n");
101
- const content = [
102
- "export const directives = [",
103
- data,
104
- "]",
105
- ""
106
- ];
107
- content.unshift(...importsContent);
108
- const code = content.join("\n");
109
- return code;
110
- };
111
- }
112
88
  function getGraphQLConfig(app) {
113
89
  const configPath = resolve(app.graphql.serverDir, "config.ts");
114
90
  app.options.virtual ??= {};
@@ -1,6 +1,6 @@
1
- import * as h36 from "h3";
1
+ import * as h31 from "h3";
2
2
 
3
3
  //#region src/routes/apollo-server.d.ts
4
- declare const _default: h36.EventHandler<h36.EventHandlerRequest, any>;
4
+ declare const _default: h31.EventHandler<h31.EventHandlerRequest, any>;
5
5
  //#endregion
6
6
  export { _default as default };
@@ -2,27 +2,21 @@ import { startServerAndCreateH3Handler } from "../utils/apollo.js";
2
2
  import defu from "defu";
3
3
  import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge";
4
4
  import { importedConfig } from "#nitro-internal-virtual/graphql-config";
5
- import { directives } from "#nitro-internal-virtual/server-directives";
6
5
  import { resolvers } from "#nitro-internal-virtual/server-resolvers";
7
6
  import { schemas } from "#nitro-internal-virtual/server-schemas";
8
7
  import { ApolloServer } from "@apollo/server";
9
8
  import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
10
- import { makeExecutableSchema } from "@graphql-tools/schema";
11
9
 
12
10
  //#region src/routes/apollo-server.ts
13
11
  function createMergedSchema() {
14
12
  try {
15
- const mergedSchemas = schemas.map((schema$1) => schema$1.def).join("\n\n");
13
+ const mergedSchemas = schemas.map((schema) => schema.def).join("\n\n");
16
14
  const typeDefs = mergeTypeDefs([mergedSchemas]);
17
15
  const mergedResolvers = mergeResolvers(resolvers.map((r) => r.resolver));
18
- let schema = makeExecutableSchema({
16
+ return {
19
17
  typeDefs,
20
18
  resolvers: mergedResolvers
21
- });
22
- if (directives && directives.length > 0) {
23
- for (const { directive } of directives) if (directive.transformer) schema = directive.transformer(schema);
24
- }
25
- return schema;
19
+ };
26
20
  } catch (error) {
27
21
  console.error("Schema merge error:", error);
28
22
  throw error;
@@ -31,9 +25,10 @@ function createMergedSchema() {
31
25
  let apolloServer = null;
32
26
  function createApolloServer() {
33
27
  if (!apolloServer) {
34
- const schema = createMergedSchema();
28
+ const { typeDefs, resolvers: mergedResolvers } = createMergedSchema();
35
29
  apolloServer = new ApolloServer(defu({
36
- schema,
30
+ typeDefs,
31
+ resolvers: mergedResolvers,
37
32
  introspection: true,
38
33
  plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })]
39
34
  }, importedConfig));
@@ -1,6 +1,6 @@
1
- import * as h32 from "h3";
1
+ import * as h33 from "h3";
2
2
 
3
3
  //#region src/routes/graphql-yoga.d.ts
4
- declare const _default: h32.EventHandler<h32.EventHandlerRequest, Promise<Response>>;
4
+ declare const _default: h33.EventHandler<h33.EventHandlerRequest, Promise<Response>>;
5
5
  //#endregion
6
6
  export { _default as default };
@@ -1,12 +1,10 @@
1
1
  import defu from "defu";
2
2
  import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge";
3
3
  import { importedConfig } from "#nitro-internal-virtual/graphql-config";
4
- import { directives } from "#nitro-internal-virtual/server-directives";
5
4
  import { resolvers } from "#nitro-internal-virtual/server-resolvers";
6
5
  import { schemas } from "#nitro-internal-virtual/server-schemas";
7
- import { makeExecutableSchema } from "@graphql-tools/schema";
8
6
  import { defineEventHandler, toWebRequest } from "h3";
9
- import { createYoga } from "graphql-yoga";
7
+ import { createSchema, createYoga } from "graphql-yoga";
10
8
 
11
9
  //#region src/routes/graphql-yoga.ts
12
10
  const apolloSandboxHtml = `<!DOCTYPE html>
@@ -28,17 +26,13 @@ new window.EmbeddedSandbox({
28
26
  </html>`;
29
27
  function createMergedSchema() {
30
28
  try {
31
- const mergedSchemas = schemas.map((schema$1) => schema$1.def).join("\n\n");
29
+ const mergedSchemas = schemas.map((schema) => schema.def).join("\n\n");
32
30
  const typeDefs = mergeTypeDefs([mergedSchemas]);
33
31
  const mergedResolvers = mergeResolvers(resolvers.map((r) => r.resolver));
34
- let schema = makeExecutableSchema({
32
+ return createSchema({
35
33
  typeDefs,
36
34
  resolvers: mergedResolvers
37
35
  });
38
- if (directives && directives.length > 0) {
39
- for (const { directive } of directives) if (directive.transformer) schema = directive.transformer(schema);
40
- }
41
- return schema;
42
36
  } catch (error) {
43
37
  console.error("Schema merge error:", error);
44
38
  throw error;
@@ -1,7 +1,7 @@
1
- import * as h34 from "h3";
1
+ import * as h36 from "h3";
2
2
 
3
3
  //#region src/routes/health.d.ts
4
- declare const _default: h34.EventHandler<h34.EventHandlerRequest, Promise<{
4
+ declare const _default: h36.EventHandler<h36.EventHandlerRequest, Promise<{
5
5
  status: string;
6
6
  message: string;
7
7
  timestamp: string;
@@ -20,7 +20,7 @@ type CodegenClientConfig = TypeScriptPluginConfig & TypeScriptDocumentsPluginCon
20
20
  interface IESMImport {
21
21
  name: string;
22
22
  as?: string;
23
- type: 'resolver' | 'query' | 'mutation' | 'type' | 'subscription' | 'directive';
23
+ type: 'resolver' | 'query' | 'mutation' | 'type' | 'subscription';
24
24
  }
25
25
  interface GenImport {
26
26
  specifier: string;
@@ -32,7 +32,6 @@ declare module 'nitropack/types' {
32
32
  scanSchemas: string[];
33
33
  scanDocuments: string[];
34
34
  scanResolvers: GenImport[];
35
- scanDirectives: GenImport[];
36
35
  graphql: {
37
36
  buildDir: string;
38
37
  watchDirs: string[];
@@ -57,23 +57,23 @@ async function loadExternalSchema(service, buildDir) {
57
57
  if (service.downloadSchema && buildDir) {
58
58
  const defaultPath = resolve(buildDir, "graphql", "schemas", `${service.name}.graphql`);
59
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
- }
60
+ if (existsSync(schemaFilePath)) try {
61
+ const result$1 = loadSchemaSync([schemaFilePath], { loaders: [new GraphQLFileLoader()] });
62
+ return result$1;
63
+ } catch {
64
+ consola$1.warn(`[graphql:${service.name}] Cached schema invalid, loading from source`);
69
65
  }
70
66
  }
71
- consola$1.info(`[graphql:${service.name}] Loading external schema from: ${schemas.join(", ")}`);
67
+ const hasUrls = schemas.some((schema) => isUrl(schema));
68
+ const hasLocalFiles = schemas.some((schema) => !isUrl(schema));
69
+ const loaders = [];
70
+ if (hasLocalFiles) loaders.push(new GraphQLFileLoader());
71
+ if (hasUrls) loaders.push(new UrlLoader());
72
+ if (loaders.length === 0) throw new Error("No appropriate loaders found for schema sources");
72
73
  const result = loadSchemaSync(schemas, {
73
- loaders: [new GraphQLFileLoader(), new UrlLoader()],
74
+ loaders,
74
75
  ...Object.keys(headers).length > 0 && { headers }
75
76
  });
76
- consola$1.info(`[graphql:${service.name}] External schema loaded successfully`);
77
77
  return result;
78
78
  } catch (error) {
79
79
  consola$1.error(`[graphql:${service.name}] Failed to load external schema:`, error);
@@ -81,6 +81,12 @@ async function loadExternalSchema(service, buildDir) {
81
81
  }
82
82
  }
83
83
  /**
84
+ * Check if a path is a URL (http/https)
85
+ */
86
+ function isUrl(path) {
87
+ return path.startsWith("http://") || path.startsWith("https://");
88
+ }
89
+ /**
84
90
  * Download and save schema from external service
85
91
  */
86
92
  async function downloadAndSaveSchema(service, buildDir) {
@@ -91,12 +97,14 @@ async function downloadAndSaveSchema(service, buildDir) {
91
97
  try {
92
98
  const headers = typeof service.headers === "function" ? service.headers() : service.headers || {};
93
99
  const schemas = Array.isArray(service.schema) ? service.schema : [service.schema];
100
+ const hasUrlSchemas = schemas.some((schema) => isUrl(schema));
101
+ const hasLocalSchemas = schemas.some((schema) => !isUrl(schema));
94
102
  let shouldDownload = false;
95
103
  const fileExists = existsSync(schemaFilePath);
96
104
  if (downloadMode === "always") {
97
105
  shouldDownload = true;
98
- if (fileExists) try {
99
- const remoteSchema = loadSchemaSync(schemas, {
106
+ if (fileExists && hasUrlSchemas) try {
107
+ const remoteSchema = loadSchemaSync(schemas.filter(isUrl), {
100
108
  loaders: [new UrlLoader()],
101
109
  ...Object.keys(headers).length > 0 && { headers }
102
110
  });
@@ -112,24 +120,48 @@ async function downloadAndSaveSchema(service, buildDir) {
112
120
  consola$1.warn(`[graphql:${service.name}] Unable to compare with remote schema, updating local cache`);
113
121
  shouldDownload = true;
114
122
  }
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
- }
123
+ else if (fileExists && hasLocalSchemas) {
124
+ const localFiles = schemas.filter((schema) => !isUrl(schema));
125
+ let sourceIsNewer = false;
126
+ for (const localFile of localFiles) if (existsSync(localFile)) {
127
+ const { statSync } = await import("node:fs");
128
+ const sourceStats = statSync(localFile);
129
+ const cachedStats = statSync(schemaFilePath);
130
+ if (sourceStats.mtime > cachedStats.mtime) {
131
+ sourceIsNewer = true;
132
+ break;
133
+ }
134
+ }
135
+ if (!sourceIsNewer) shouldDownload = false;
136
+ }
137
+ } else if (downloadMode === true || downloadMode === "once") shouldDownload = !fileExists;
119
138
  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`);
139
+ if (hasUrlSchemas && hasLocalSchemas) {
140
+ const schema = loadSchemaSync(schemas, {
141
+ loaders: [new GraphQLFileLoader(), new UrlLoader()],
142
+ ...Object.keys(headers).length > 0 && { headers }
143
+ });
144
+ const schemaString = printSchemaWithDirectives(schema);
145
+ mkdirSync(dirname(schemaFilePath), { recursive: true });
146
+ writeFileSync(schemaFilePath, schemaString, "utf-8");
147
+ } else if (hasUrlSchemas) {
148
+ const schema = loadSchemaSync(schemas, {
149
+ loaders: [new UrlLoader()],
150
+ ...Object.keys(headers).length > 0 && { headers }
151
+ });
152
+ const schemaString = printSchemaWithDirectives(schema);
153
+ mkdirSync(dirname(schemaFilePath), { recursive: true });
154
+ writeFileSync(schemaFilePath, schemaString, "utf-8");
155
+ } else if (hasLocalSchemas) {
156
+ const schema = loadSchemaSync(schemas, { loaders: [new GraphQLFileLoader()] });
157
+ const schemaString = printSchemaWithDirectives(schema);
158
+ mkdirSync(dirname(schemaFilePath), { recursive: true });
159
+ writeFileSync(schemaFilePath, schemaString, "utf-8");
160
+ }
129
161
  }
130
162
  return schemaFilePath;
131
163
  } catch (error) {
132
- consola$1.error(`[graphql:${service.name}] Failed to download schema:`, error);
164
+ consola$1.error(`[graphql:${service.name}] Failed to download/copy schema:`, error);
133
165
  return void 0;
134
166
  }
135
167
  }
@@ -143,13 +175,11 @@ async function loadGraphQLDocuments(patterns) {
143
175
  }
144
176
  }
145
177
  async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, outputPath, serviceName) {
146
- if (docs.length === 0) {
147
- const serviceLabel$1 = serviceName ? `:${serviceName}` : "";
148
- consola$1.info(`[graphql${serviceLabel$1}] No client GraphQL files found. Skipping client type generation.`);
178
+ if (docs.length === 0 && !serviceName) {
179
+ consola$1.info("No client GraphQL files found. Skipping client type generation.");
149
180
  return false;
150
181
  }
151
182
  const serviceLabel = serviceName ? `:${serviceName}` : "";
152
- consola$1.info(`[graphql${serviceLabel}] Found ${docs.length} client GraphQL documents`);
153
183
  const defaultConfig = {
154
184
  emitLegacyCommonJSImports: false,
155
185
  useTypeImports: true,
@@ -178,6 +208,48 @@ async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, ou
178
208
  const mergedConfig = defu$1(defaultConfig, config);
179
209
  const mergedSdkConfig = defu$1(mergedConfig, sdkConfig);
180
210
  try {
211
+ if (docs.length === 0) {
212
+ const output$1 = await codegen({
213
+ filename: outputPath || "client-types.generated.ts",
214
+ schema: parse(printSchemaWithDirectives(schema)),
215
+ documents: [],
216
+ config: mergedConfig,
217
+ plugins: [{ pluginContent: {} }, { typescript: {} }],
218
+ pluginMap: {
219
+ pluginContent: { plugin: pluginContent },
220
+ typescript: { plugin }
221
+ }
222
+ });
223
+ const sdkContent$1 = `// THIS FILE IS GENERATED, DO NOT EDIT!
224
+ /* eslint-disable eslint-comments/no-unlimited-disable */
225
+ /* tslint:disable */
226
+ /* eslint-disable */
227
+ /* prettier-ignore */
228
+
229
+ import type { GraphQLResolveInfo } from 'graphql'
230
+ export type RequireFields<T, K extends keyof T> = Omit<T, K> & { [P in K]-?: NonNullable<T[P]> }
231
+
232
+ export interface Requester<C = {}, E = unknown> {
233
+ <R, V>(doc: string, vars?: V, options?: C): Promise<R> | AsyncIterable<R>
234
+ }
235
+
236
+ export type Sdk = {
237
+ request: <R, V = Record<string, any>>(document: string, variables?: V) => Promise<R>
238
+ }
239
+
240
+ export function getSdk(requester: Requester): Sdk {
241
+ return {
242
+ request: <R, V = Record<string, any>>(document: string, variables?: V): Promise<R> => {
243
+ return requester<R, V>(document, variables)
244
+ }
245
+ }
246
+ }
247
+ `;
248
+ return {
249
+ types: output$1,
250
+ sdk: sdkContent$1
251
+ };
252
+ }
181
253
  const output = await codegen({
182
254
  filename: outputPath || "client-types.generated.ts",
183
255
  schema: parse(printSchemaWithDirectives(schema)),
@@ -1,4 +1,3 @@
1
- import { GraphQLSchema } from "graphql";
2
1
  import { ApolloServerOptions } from "@apollo/server";
3
2
  import { H3Event } from "h3";
4
3
  import { YogaServerOptions } from "graphql-yoga";
@@ -18,55 +17,5 @@ declare function defineSubscription(resolvers?: Resolvers['Subscription']): Reso
18
17
  declare function defineType(resolvers: Resolvers): Resolvers;
19
18
  type DefineServerConfig<T extends NPMConfig = NPMConfig> = T['framework'] extends 'graphql-yoga' ? Partial<YogaServerOptions<H3Event, Partial<H3Event>>> : T['framework'] extends 'apollo-server' ? Partial<ApolloServerOptions<H3Event>> : Partial<YogaServerOptions<H3Event, Partial<H3Event>>> | Partial<ApolloServerOptions<H3Event>>;
20
19
  declare function defineGraphQLConfig<T extends NPMConfig = NPMConfig>(config: Partial<DefineServerConfig<T>>): Partial<DefineServerConfig<T>>;
21
- type DirectiveLocationName = 'QUERY' | 'MUTATION' | 'SUBSCRIPTION' | 'FIELD' | 'FRAGMENT_DEFINITION' | 'FRAGMENT_SPREAD' | 'INLINE_FRAGMENT' | 'VARIABLE_DEFINITION' | 'SCHEMA' | 'SCALAR' | 'OBJECT' | 'FIELD_DEFINITION' | 'ARGUMENT_DEFINITION' | 'INTERFACE' | 'UNION' | 'ENUM' | 'ENUM_VALUE' | 'INPUT_OBJECT' | 'INPUT_FIELD_DEFINITION';
22
- type GraphQLScalarType = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID' | 'JSON' | 'DateTime';
23
- type GraphQLBaseType = GraphQLScalarType | (string & {});
24
- type GraphQLArgumentType = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID' | 'JSON' | 'DateTime' | 'String!' | 'Int!' | 'Float!' | 'Boolean!' | 'ID!' | 'JSON!' | 'DateTime!' | '[String]' | '[String!]' | '[String]!' | '[String!]!' | '[Int]' | '[Int!]' | '[Int]!' | '[Int!]!' | '[Float]' | '[Float!]' | '[Float]!' | '[Float!]!' | '[Boolean]' | '[Boolean!]' | '[Boolean]!' | '[Boolean!]!' | '[ID]' | '[ID!]' | '[ID]!' | '[ID!]!' | '[JSON]' | '[JSON!]' | '[JSON]!' | '[JSON!]!' | '[DateTime]' | '[DateTime!]' | '[DateTime]!' | '[DateTime!]!' | (string & {});
25
- interface DirectiveArgument<T extends GraphQLArgumentType = GraphQLArgumentType> {
26
- /**
27
- * GraphQL type for the argument
28
- * @example 'String', 'Int!', '[String!]!', 'DateTime', 'JSON'
29
- */
30
- type: T;
31
- defaultValue?: any;
32
- description?: string;
33
- }
34
- interface DirectiveArg {
35
- type: GraphQLArgumentType;
36
- defaultValue?: any;
37
- description?: string;
38
- }
39
- interface DirectiveDefinition {
40
- name: string;
41
- locations: DirectiveLocationName[];
42
- args?: Record<string, DirectiveArg>;
43
- description?: string;
44
- isRepeatable?: boolean;
45
- transformer?: (schema: GraphQLSchema) => GraphQLSchema;
46
- }
47
- interface DefineDirectiveConfig {
48
- name: string;
49
- locations: ReadonlyArray<DirectiveLocationName>;
50
- args?: Record<string, {
51
- type: GraphQLArgumentType;
52
- defaultValue?: any;
53
- description?: string;
54
- }>;
55
- description?: string;
56
- isRepeatable?: boolean;
57
- transformer?: (schema: GraphQLSchema) => GraphQLSchema;
58
- }
59
- /**
60
- * Helper function to create directive arguments with proper type inference
61
- * @example
62
- * args: {
63
- * myArg: arg('String!', { defaultValue: 'hello' })
64
- * }
65
- */
66
- declare function arg<T extends GraphQLArgumentType>(type: T, options?: {
67
- defaultValue?: any;
68
- description?: string;
69
- }): DirectiveArgument<T>;
70
- declare function defineDirective(config: DefineDirectiveConfig): DirectiveDefinition;
71
20
  //#endregion
72
- export { DefineDirectiveConfig, DefineServerConfig, DirectiveArgument, DirectiveDefinition, GraphQLArgumentType, GraphQLBaseType, GraphQLScalarType, ResolverQuery, arg, defineDirective, defineGraphQLConfig, defineMutation, defineQuery, defineResolver, defineSchema, defineSubscription, defineType };
21
+ export { DefineServerConfig, ResolverQuery, defineGraphQLConfig, defineMutation, defineQuery, defineResolver, defineSchema, defineSubscription, defineType };
@@ -20,38 +20,6 @@ function defineType(resolvers) {
20
20
  function defineGraphQLConfig(config) {
21
21
  return config;
22
22
  }
23
- /**
24
- * Helper function to create directive arguments with proper type inference
25
- * @example
26
- * args: {
27
- * myArg: arg('String!', { defaultValue: 'hello' })
28
- * }
29
- */
30
- function arg(type, options) {
31
- return {
32
- type,
33
- ...options
34
- };
35
- }
36
- function defineDirective(config) {
37
- const args = config.args ? Object.entries(config.args).map(([name, arg$1]) => {
38
- const defaultValue = arg$1.defaultValue !== void 0 ? ` = ${JSON.stringify(arg$1.defaultValue)}` : "";
39
- return `${name}: ${arg$1.type}${defaultValue}`;
40
- }).join(", ") : "";
41
- const argsString = args ? `(${args})` : "";
42
- const locations = config.locations.join(" | ");
43
- const schemaDefinition = `directive @${config.name}${argsString} on ${locations}`;
44
- Object.defineProperty(config, "__schema", {
45
- value: schemaDefinition,
46
- enumerable: false,
47
- configurable: false,
48
- writable: false
49
- });
50
- return {
51
- ...config,
52
- locations: [...config.locations]
53
- };
54
- }
55
23
 
56
24
  //#endregion
57
- export { arg, defineDirective, defineGraphQLConfig, defineMutation, defineQuery, defineResolver, defineSchema, defineSubscription, defineType };
25
+ export { defineGraphQLConfig, defineMutation, defineQuery, defineResolver, defineSchema, defineSubscription, defineType };
@@ -1,5 +1,4 @@
1
1
  import { GenImport } from "../types/index.js";
2
- import { directiveParser, generateDirectiveSchema, generateDirectiveSchemas } from "./directive-parser.js";
3
2
  import { Nitro } from "nitropack";
4
3
 
5
4
  //#region src/utils/index.d.ts
@@ -8,7 +7,7 @@ declare function getImportId(p: string, lazy?: boolean): string;
8
7
  declare function relativeWithDot(from: string, to: string): string;
9
8
  declare function scanGraphql(nitro: Nitro): Promise<string[]>;
10
9
  declare function scanResolvers(nitro: Nitro): Promise<GenImport[]>;
11
- declare function scanDirectives(nitro: Nitro): Promise<GenImport[]>;
10
+ declare function scanDirectives(nitro: Nitro): Promise<string[]>;
12
11
  declare function scanTypeDefs(nitro: Nitro): Promise<string[]>;
13
12
  declare function scanSchemas(nitro: Nitro): Promise<string[]>;
14
13
  declare function scanDocs(nitro: Nitro): Promise<string[]>;
@@ -21,4 +20,4 @@ declare function scanExternalServiceDocs(nitro: Nitro, serviceName: string, patt
21
20
  */
22
21
  declare function validateExternalServices(services: any[]): string[];
23
22
  //#endregion
24
- export { GLOB_SCAN_PATTERN, directiveParser, generateDirectiveSchema, generateDirectiveSchemas, getImportId, relativeWithDot, scanDirectives, scanDocs, scanExternalServiceDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs, validateExternalServices };
23
+ export { GLOB_SCAN_PATTERN, getImportId, relativeWithDot, scanDirectives, scanDocs, scanExternalServiceDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs, validateExternalServices };
@@ -1,4 +1,3 @@
1
- import { directiveParser, generateDirectiveSchema, generateDirectiveSchemas } from "./directive-parser.js";
2
1
  import { join, relative } from "pathe";
3
2
  import { readFile } from "node:fs/promises";
4
3
  import { hash } from "ohash";
@@ -57,11 +56,6 @@ async function scanResolvers(nitro) {
57
56
  type: "subscription",
58
57
  as: `_${hash(decl.id.name + file.path).replace(/-/g, "").slice(0, 6)}`
59
58
  });
60
- if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineDirective") exports.imports.push({
61
- name: decl.id.name,
62
- type: "directive",
63
- as: `_${hash(decl.id.name + file.path).replace(/-/g, "").slice(0, 6)}`
64
- });
65
59
  }
66
60
  }
67
61
  }
@@ -71,28 +65,7 @@ async function scanResolvers(nitro) {
71
65
  }
72
66
  async function scanDirectives(nitro) {
73
67
  const files = await scanFiles(nitro, "graphql", "**/*.directive.{ts,js}");
74
- const exportName = [];
75
- for (const file of files) {
76
- const fileContent = await readFile(file.fullPath, "utf-8");
77
- const parsed = await parseAsync(file.fullPath, fileContent);
78
- const exports = {
79
- imports: [],
80
- specifier: file.fullPath
81
- };
82
- for (const node of parsed.program.body) if (node.type === "ExportNamedDeclaration" && node.declaration && node.declaration.type === "VariableDeclaration") {
83
- for (const decl of node.declaration.declarations) if (decl.type === "VariableDeclarator" && decl.init && decl.id.type === "Identifier") {
84
- if (decl.init && decl.init.type === "CallExpression") {
85
- if (decl.init.callee.type === "Identifier" && decl.init.callee.name === "defineDirective") exports.imports.push({
86
- name: decl.id.name,
87
- type: "directive",
88
- as: `_${hash(decl.id.name + file.path).replace(/-/g, "").slice(0, 6)}`
89
- });
90
- }
91
- }
92
- }
93
- if (exports.imports.length > 0) exportName.push(exports);
94
- }
95
- return exportName;
68
+ return files.map((f) => f.fullPath);
96
69
  }
97
70
  async function scanTypeDefs(nitro) {
98
71
  const files = await scanFiles(nitro, "graphql", "**/*.typedef.{ts,js}");
@@ -173,4 +146,4 @@ async function scanDir(nitro, dir, name, globPattern = GLOB_SCAN_PATTERN) {
173
146
  }
174
147
 
175
148
  //#endregion
176
- export { GLOB_SCAN_PATTERN, directiveParser, generateDirectiveSchema, generateDirectiveSchemas, getImportId, relativeWithDot, scanDirectives, scanDocs, scanExternalServiceDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs, validateExternalServices };
149
+ export { GLOB_SCAN_PATTERN, getImportId, relativeWithDot, scanDirectives, scanDocs, scanExternalServiceDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs, validateExternalServices };
@@ -116,7 +116,8 @@ async function serverTypeGeneration(app) {
116
116
  }
117
117
  async function clientTypeGeneration(nitro) {
118
118
  try {
119
- await generateMainClientTypes(nitro);
119
+ const hasServerSchema = nitro.scanSchemas && nitro.scanSchemas.length > 0;
120
+ if (hasServerSchema) await generateMainClientTypes(nitro);
120
121
  if (nitro.options.graphql?.externalServices?.length) await generateExternalServicesTypes(nitro);
121
122
  } catch (error) {
122
123
  consola.error("Client schema generation error:", error);
@@ -190,8 +191,13 @@ async function generateExternalServicesTypes(nitro) {
190
191
  let loadDocs = [];
191
192
  if (documentPatterns.length > 0) try {
192
193
  loadDocs = await loadGraphQLDocuments(documentPatterns);
194
+ if (!loadDocs || loadDocs.length === 0) {
195
+ consola.warn(`[graphql:${service.name}] No GraphQL documents found, skipping service generation`);
196
+ continue;
197
+ }
193
198
  } catch (error) {
194
- consola.warn(`[graphql:${service.name}] No documents found:`, error);
199
+ consola.warn(`[graphql:${service.name}] No documents found, skipping service generation:`, error);
200
+ continue;
195
201
  }
196
202
  const types = await generateExternalClientTypes(service, schema, loadDocs);
197
203
  if (types === false) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nitro-graphql",
3
3
  "type": "module",
4
- "version": "1.2.1",
4
+ "version": "1.2.3",
5
5
  "description": "GraphQL integration for Nitro",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
@@ -1,80 +0,0 @@
1
- //#region src/utils/directive-parser.d.ts
2
- interface ParsedDirective {
3
- name: string;
4
- locations: string[];
5
- args?: Record<string, {
6
- type: string;
7
- defaultValue?: any;
8
- }>;
9
- description?: string;
10
- isRepeatable?: boolean;
11
- }
12
- /**
13
- * Clean AST-based directive parser using oxc-parser
14
- */
15
- declare class DirectiveParser {
16
- private oxc;
17
- init(): Promise<void>;
18
- /**
19
- * Parse directives from a TypeScript/JavaScript file
20
- */
21
- parseDirectives(fileContent: string, filePath: string): Promise<ParsedDirective[]>;
22
- /**
23
- * Extract directive definitions from AST
24
- */
25
- private extractDirectiveDefinitions;
26
- /**
27
- * Traverse AST nodes recursively
28
- */
29
- private traverse;
30
- /**
31
- * Check if node is a defineDirective call
32
- */
33
- private isDefineDirectiveCall;
34
- /**
35
- * Extract directive configuration from defineDirective call
36
- */
37
- private extractDirectiveFromCall;
38
- /**
39
- * Extract directive properties from object expression
40
- */
41
- private extractDirectiveFromObject;
42
- /**
43
- * Extract string literal value
44
- */
45
- private extractStringLiteral;
46
- /**
47
- * Extract boolean literal value
48
- */
49
- private extractBooleanLiteral;
50
- /**
51
- * Extract array of strings
52
- */
53
- private extractStringArray;
54
- /**
55
- * Extract arguments object
56
- */
57
- private extractArgsObject;
58
- /**
59
- * Extract argument configuration
60
- */
61
- private extractArgConfig;
62
- /**
63
- * Extract literal value (string, number, boolean)
64
- */
65
- private extractLiteralValue;
66
- }
67
- /**
68
- * Generate GraphQL directive schema from parsed directive
69
- */
70
- declare function generateDirectiveSchema(directive: ParsedDirective): string;
71
- /**
72
- * Generate directive schemas file from scanned directives
73
- */
74
- declare function generateDirectiveSchemas(nitro: any, directives: any[]): Promise<void>;
75
- /**
76
- * Singleton instance for reuse
77
- */
78
- declare const directiveParser: DirectiveParser;
79
- //#endregion
80
- export { DirectiveParser, ParsedDirective, directiveParser, generateDirectiveSchema, generateDirectiveSchemas };
@@ -1,235 +0,0 @@
1
- //#region src/utils/directive-parser.ts
2
- /**
3
- * Clean AST-based directive parser using oxc-parser
4
- */
5
- var DirectiveParser = class {
6
- oxc;
7
- async init() {
8
- if (!this.oxc) this.oxc = await import("oxc-parser");
9
- }
10
- /**
11
- * Parse directives from a TypeScript/JavaScript file
12
- */
13
- async parseDirectives(fileContent, filePath) {
14
- await this.init();
15
- try {
16
- const result = this.oxc.parseSync(filePath, fileContent, {
17
- lang: filePath.endsWith(".ts") ? "ts" : "js",
18
- sourceType: "module",
19
- astType: "ts"
20
- });
21
- if (result.errors.length > 0) {
22
- console.warn(`Parse errors in ${filePath}:`, result.errors.map((e) => e.message));
23
- return [];
24
- }
25
- return this.extractDirectiveDefinitions(result.program);
26
- } catch (error) {
27
- console.warn(`Failed to parse ${filePath} with oxc:`, error);
28
- return [];
29
- }
30
- }
31
- /**
32
- * Extract directive definitions from AST
33
- */
34
- extractDirectiveDefinitions(program) {
35
- const directives = [];
36
- this.traverse(program, (node) => {
37
- if (this.isDefineDirectiveCall(node)) {
38
- const directive = this.extractDirectiveFromCall(node);
39
- if (directive) directives.push(directive);
40
- }
41
- });
42
- return directives;
43
- }
44
- /**
45
- * Traverse AST nodes recursively
46
- */
47
- traverse(node, visitor) {
48
- if (!node || typeof node !== "object") return;
49
- visitor(node);
50
- for (const key in node) {
51
- const child = node[key];
52
- if (Array.isArray(child)) child.forEach((item) => this.traverse(item, visitor));
53
- else if (child && typeof child === "object") this.traverse(child, visitor);
54
- }
55
- }
56
- /**
57
- * Check if node is a defineDirective call
58
- */
59
- isDefineDirectiveCall(node) {
60
- return node.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "defineDirective" && node.arguments?.length > 0;
61
- }
62
- /**
63
- * Extract directive configuration from defineDirective call
64
- */
65
- extractDirectiveFromCall(node) {
66
- const arg = node.arguments[0];
67
- if (arg?.type !== "ObjectExpression") return null;
68
- return this.extractDirectiveFromObject(arg);
69
- }
70
- /**
71
- * Extract directive properties from object expression
72
- */
73
- extractDirectiveFromObject(objNode) {
74
- let name = "";
75
- let locations = [];
76
- let args = {};
77
- let description;
78
- let isRepeatable;
79
- for (const prop of objNode.properties || []) {
80
- if (prop.type !== "Property" || prop.key?.type !== "Identifier") continue;
81
- switch (prop.key.name) {
82
- case "name":
83
- name = this.extractStringLiteral(prop.value) || "";
84
- break;
85
- case "locations":
86
- locations = this.extractStringArray(prop.value);
87
- break;
88
- case "args":
89
- args = this.extractArgsObject(prop.value);
90
- break;
91
- case "description":
92
- description = this.extractStringLiteral(prop.value);
93
- break;
94
- case "isRepeatable":
95
- isRepeatable = this.extractBooleanLiteral(prop.value);
96
- break;
97
- }
98
- }
99
- return name && locations.length > 0 ? {
100
- name,
101
- locations,
102
- args,
103
- description,
104
- isRepeatable
105
- } : null;
106
- }
107
- /**
108
- * Extract string literal value
109
- */
110
- extractStringLiteral(node) {
111
- if (node?.type === "Literal" && typeof node.value === "string") return node.value;
112
- return void 0;
113
- }
114
- /**
115
- * Extract boolean literal value
116
- */
117
- extractBooleanLiteral(node) {
118
- if (node?.type === "Literal" && typeof node.value === "boolean") return node.value;
119
- return void 0;
120
- }
121
- /**
122
- * Extract array of strings
123
- */
124
- extractStringArray(node) {
125
- if (node?.type !== "ArrayExpression") return [];
126
- return (node.elements || []).filter((el) => el?.type === "Literal" && typeof el.value === "string").map((el) => el.value);
127
- }
128
- /**
129
- * Extract arguments object
130
- */
131
- extractArgsObject(node) {
132
- if (node?.type !== "ObjectExpression") return {};
133
- const args = {};
134
- for (const prop of node.properties || []) {
135
- if (prop.type !== "Property" || prop.key?.type !== "Identifier") continue;
136
- const argName = prop.key.name;
137
- const argConfig = this.extractArgConfig(prop.value);
138
- if (argConfig) args[argName] = argConfig;
139
- }
140
- return args;
141
- }
142
- /**
143
- * Extract argument configuration
144
- */
145
- extractArgConfig(node) {
146
- if (node?.type !== "ObjectExpression") return null;
147
- let type = "String";
148
- let defaultValue;
149
- for (const prop of node.properties || []) {
150
- if (prop.type !== "Property" || prop.key?.type !== "Identifier") continue;
151
- switch (prop.key.name) {
152
- case "type": {
153
- const typeValue = this.extractStringLiteral(prop.value);
154
- if (typeValue) type = typeValue;
155
- break;
156
- }
157
- case "defaultValue":
158
- defaultValue = this.extractLiteralValue(prop.value);
159
- break;
160
- }
161
- }
162
- return {
163
- type,
164
- ...defaultValue !== void 0 && { defaultValue }
165
- };
166
- }
167
- /**
168
- * Extract literal value (string, number, boolean)
169
- */
170
- extractLiteralValue(node) {
171
- if (node?.type === "Literal") return node.value;
172
- return void 0;
173
- }
174
- };
175
- /**
176
- * Generate GraphQL directive schema from parsed directive
177
- */
178
- function generateDirectiveSchema(directive) {
179
- let args = "";
180
- if (directive.args && Object.keys(directive.args).length > 0) {
181
- const argDefs = Object.entries(directive.args).map(([name, arg]) => {
182
- let defaultValue = "";
183
- if (arg.defaultValue !== void 0) if (typeof arg.defaultValue === "string") defaultValue = ` = "${arg.defaultValue}"`;
184
- else defaultValue = ` = ${arg.defaultValue}`;
185
- return `${name}: ${arg.type}${defaultValue}`;
186
- });
187
- args = `(${argDefs.join(", ")})`;
188
- }
189
- const locations = directive.locations.join(" | ");
190
- return `directive @${directive.name}${args} on ${locations}`;
191
- }
192
- /**
193
- * Generate directive schemas file from scanned directives
194
- */
195
- async function generateDirectiveSchemas(nitro, directives) {
196
- if (directives.length === 0) return;
197
- const { existsSync, readFileSync, writeFileSync } = await import("node:fs");
198
- const { readFile } = await import("node:fs/promises");
199
- const { resolve } = await import("pathe");
200
- const directiveSchemas = [];
201
- const seenDirectives = /* @__PURE__ */ new Set();
202
- const parser = new DirectiveParser();
203
- for (const dir of directives) for (const _imp of dir.imports) {
204
- const fileContent = await readFile(dir.specifier, "utf-8");
205
- const directiveDefs = await parser.parseDirectives(fileContent, dir.specifier);
206
- for (const def of directiveDefs) {
207
- if (seenDirectives.has(def.name)) continue;
208
- seenDirectives.add(def.name);
209
- const schema = generateDirectiveSchema(def);
210
- directiveSchemas.push(schema);
211
- }
212
- }
213
- if (directiveSchemas.length > 0) {
214
- const directivesPath = resolve(nitro.graphql.serverDir, "_directives.graphql");
215
- const content = `# WARNING: This file is auto-generated by nitro-graphql
216
- # Do not modify this file directly. It will be overwritten.
217
- # To define custom directives, create .directive.ts files using defineDirective()
218
-
219
- ${directiveSchemas.join("\n\n")}`;
220
- let shouldWrite = true;
221
- if (existsSync(directivesPath)) {
222
- const existingContent = readFileSync(directivesPath, "utf-8");
223
- shouldWrite = existingContent !== content;
224
- }
225
- if (shouldWrite) writeFileSync(directivesPath, content, "utf-8");
226
- if (!nitro.scanSchemas.includes(directivesPath)) nitro.scanSchemas.push(directivesPath);
227
- }
228
- }
229
- /**
230
- * Singleton instance for reuse
231
- */
232
- const directiveParser = new DirectiveParser();
233
-
234
- //#endregion
235
- export { DirectiveParser, directiveParser, generateDirectiveSchema, generateDirectiveSchemas };