nitro-graphql 1.1.2 → 1.1.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,6 +30,7 @@
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
33
34
 
34
35
  ## 🚀 Quick Start
35
36
 
@@ -181,6 +182,10 @@ server/
181
182
  ├── graphql/
182
183
  │ ├── schema.graphql # Main schema with scalars and base types
183
184
  │ ├── hello.resolver.ts # Global resolvers (use named exports)
185
+ │ ├── directives/ # Custom GraphQL directives
186
+ │ │ ├── auth.directive.ts # Authentication directive
187
+ │ │ ├── cache.directive.ts # Caching directive
188
+ │ │ └── validate.directive.ts # Validation directive
184
189
  │ ├── users/
185
190
  │ │ ├── user.graphql # User schema definitions
186
191
  │ │ ├── user-queries.resolver.ts # User query resolvers (use named exports)
@@ -615,6 +620,90 @@ export const postTypes = defineType({
615
620
 
616
621
  </details>
617
622
 
623
+ <details>
624
+ <summary><strong>defineDirective</strong> - Create custom GraphQL directives</summary>
625
+
626
+ ```ts
627
+ import { defineDirective } from 'nitro-graphql/utils/define'
628
+ import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'
629
+ import { defaultFieldResolver, GraphQLError } from 'graphql'
630
+
631
+ export const authDirective = defineDirective({
632
+ name: 'auth',
633
+ locations: ['FIELD_DEFINITION', 'OBJECT'],
634
+ args: {
635
+ requires: {
636
+ type: 'String',
637
+ defaultValue: 'USER',
638
+ description: 'Required role to access this field',
639
+ },
640
+ },
641
+ description: 'Directive to check authentication and authorization',
642
+ transformer: (schema) => {
643
+ return mapSchema(schema, {
644
+ [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
645
+ const authDirectiveConfig = getDirective(schema, fieldConfig, 'auth')?.[0]
646
+
647
+ if (authDirectiveConfig) {
648
+ const { resolve = defaultFieldResolver } = fieldConfig
649
+
650
+ fieldConfig.resolve = async function (source, args, context, info) {
651
+ if (!context.user) {
652
+ throw new GraphQLError('You must be logged in')
653
+ }
654
+
655
+ if (context.user.role !== authDirectiveConfig.requires) {
656
+ throw new GraphQLError('Insufficient permissions')
657
+ }
658
+
659
+ return resolve(source, args, context, info)
660
+ }
661
+ }
662
+
663
+ return fieldConfig
664
+ },
665
+ })
666
+ },
667
+ })
668
+ ```
669
+
670
+ **Usage in Schema:**
671
+ ```graphql
672
+ type User {
673
+ id: ID!
674
+ name: String!
675
+ email: String! @auth(requires: "ADMIN")
676
+ secretData: String @auth(requires: "SUPER_ADMIN")
677
+ }
678
+
679
+ type Query {
680
+ users: [User!]! @auth
681
+ adminStats: AdminStats @auth(requires: "ADMIN")
682
+ }
683
+ ```
684
+
685
+ **Available Argument Types:**
686
+ - Basic scalars: `String`, `Int`, `Float`, `Boolean`, `ID`, `JSON`, `DateTime`
687
+ - Non-nullable: `String!`, `Int!`, `Float!`, `Boolean!`, `ID!`, `JSON!`, `DateTime!`
688
+ - Arrays: `[String]`, `[String!]`, `[String]!`, `[String!]!` (and all combinations for other types)
689
+ - Custom types: Any string for your custom GraphQL types
690
+
691
+ **Helper Function:**
692
+ ```ts
693
+ export const validateDirective = defineDirective({
694
+ name: 'validate',
695
+ locations: ['FIELD_DEFINITION', 'ARGUMENT_DEFINITION'],
696
+ args: {
697
+ minLength: arg('Int', { description: 'Minimum length' }),
698
+ maxLength: arg('Int', { description: 'Maximum length' }),
699
+ pattern: arg('String', { description: 'Regex pattern' }),
700
+ },
701
+ // ... transformer implementation
702
+ })
703
+ ```
704
+
705
+ </details>
706
+
618
707
  <details>
619
708
  <summary><strong>defineSchema</strong> - Define custom schema with validation</summary>
620
709
 
@@ -741,6 +830,75 @@ export default defineNitroConfig({
741
830
 
742
831
  ## 🔥 Advanced Features
743
832
 
833
+ <details>
834
+ <summary><strong>Custom Directives</strong></summary>
835
+
836
+ Create reusable GraphQL directives with automatic schema generation:
837
+
838
+ ```ts
839
+ // server/graphql/directives/auth.directive.ts
840
+ import { defineDirective } from 'nitro-graphql/utils/define'
841
+ import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'
842
+
843
+ export const authDirective = defineDirective({
844
+ name: 'auth',
845
+ locations: ['FIELD_DEFINITION', 'OBJECT'],
846
+ args: {
847
+ requires: {
848
+ type: 'String',
849
+ defaultValue: 'USER',
850
+ description: 'Required role to access this field',
851
+ },
852
+ },
853
+ description: 'Authentication and authorization directive',
854
+ transformer: (schema) => {
855
+ return mapSchema(schema, {
856
+ [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
857
+ const authConfig = getDirective(schema, fieldConfig, 'auth')?.[0]
858
+ if (authConfig) {
859
+ // Transform field resolvers to check authentication
860
+ const { resolve = defaultFieldResolver } = fieldConfig
861
+ fieldConfig.resolve = async (source, args, context, info) => {
862
+ if (!context.user || context.user.role !== authConfig.requires) {
863
+ throw new GraphQLError('Access denied')
864
+ }
865
+ return resolve(source, args, context, info)
866
+ }
867
+ }
868
+ return fieldConfig
869
+ },
870
+ })
871
+ },
872
+ })
873
+ ```
874
+
875
+ **Common Directive Examples:**
876
+ - `@auth(requires: "ADMIN")` - Role-based authentication
877
+ - `@cache(ttl: 300, scope: "PUBLIC")` - Field-level caching
878
+ - `@rateLimit(limit: 10, window: 60)` - Rate limiting
879
+ - `@validate(minLength: 5, maxLength: 100)` - Input validation
880
+ - `@transform(upper: true, trim: true)` - Data transformation
881
+ - `@permission(roles: ["ADMIN", "MODERATOR"])` - Multi-role permissions
882
+
883
+ **Usage in Schema:**
884
+ ```graphql
885
+ type User {
886
+ id: ID!
887
+ name: String!
888
+ email: String! @auth(requires: "ADMIN")
889
+ posts: [Post!]! @cache(ttl: 300)
890
+ }
891
+
892
+ type Query {
893
+ users: [User!]! @rateLimit(limit: 100, window: 3600)
894
+ sensitiveData: String @auth(requires: "SUPER_ADMIN")
895
+ }
896
+ ```
897
+
898
+ The module automatically generates the directive schema definitions and integrates them with both GraphQL Yoga and Apollo Server.
899
+
900
+ </details>
901
+
744
902
  <details>
745
903
  <summary><strong>Custom Scalars</strong></summary>
746
904
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { StandardSchemaV1 } from "./types/standard-schema.js";
2
2
  import { CodegenClientConfig, CodegenServerConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions } from "./types/index.js";
3
- import * as nitropack3 from "nitropack";
3
+ import * as nitropack1 from "nitropack";
4
4
 
5
5
  //#region src/index.d.ts
6
- declare const _default: nitropack3.NitroModule;
6
+ declare const _default: nitropack1.NitroModule;
7
7
  //#endregion
8
8
  export { CodegenClientConfig, CodegenServerConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, StandardSchemaV1, _default as default };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
+ import { generateDirectiveSchemas } from "./utils/directive-parser.js";
1
2
  import { relativeWithDot, scanDirectives, scanDocs, scanResolvers, scanSchemas } from "./utils/index.js";
2
3
  import { clientTypeGeneration, serverTypeGeneration } from "./utils/type-generation.js";
3
4
  import { rollupConfig } from "./rollup.js";
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { readFile } from "node:fs/promises";
5
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { watch } from "chokidar";
8
8
  import consola from "consola";
@@ -81,56 +81,7 @@ var src_default = defineNitroModule({
81
81
  nitro.scanResolvers = resolvers;
82
82
  const directives = await scanDirectives(nitro);
83
83
  nitro.scanDirectives = directives;
84
- if (directives.length > 0) {
85
- const directiveSchemas = [];
86
- for (const dir of directives) for (const _imp of dir.imports) {
87
- const fileContent = await readFile(dir.specifier, "utf-8");
88
- const nameMatch = fileContent.match(/name:\s*['"`](\w+)['"`]/);
89
- const locationsMatch = fileContent.match(/locations:\s*\[([\s\S]*?)\]/);
90
- const argsMatch = fileContent.match(/args:\s*\{([\s\S]*?)\}\s*,\s*(?:description|transformer)/);
91
- if (nameMatch && locationsMatch) {
92
- const name = nameMatch[1];
93
- const locations = locationsMatch?.[1]?.split(",").map((l) => l.trim().replace(/['"`]/g, "")).filter(Boolean).join(" | ") || "";
94
- let args = "";
95
- if (argsMatch && argsMatch[1] && argsMatch[1].trim()) {
96
- const argDefs = [];
97
- const argMatches = argsMatch[1].matchAll(/(\w+):\s*\{([^}]+)\}/g);
98
- for (const argMatch of argMatches) {
99
- const argName = argMatch[1];
100
- const argBody = argMatch[2];
101
- const typeMatch = argBody?.match(/type:\s*['"`](\[[\w!]+\]|\w+)['"`]/);
102
- const type = typeMatch ? typeMatch[1] : "String";
103
- const defaultMatch = argBody?.match(/defaultValue:\s*(['"`]([^'"`]+)['"`]|(\d+)|true|false)/);
104
- let defaultValue = "";
105
- if (defaultMatch) {
106
- const value = defaultMatch[2] || defaultMatch[3] || defaultMatch[1]?.replace(/['"`]/g, "");
107
- if (type === "String") defaultValue = ` = "${value}"`;
108
- else if (type === "Int" || type === "Float") defaultValue = ` = ${value}`;
109
- else if (type === "Boolean") defaultValue = ` = ${value}`;
110
- }
111
- argDefs.push(`${argName}: ${type}${defaultValue}`);
112
- }
113
- if (argDefs.length > 0) args = `(${argDefs.join(", ")})`;
114
- }
115
- directiveSchemas.push(`directive @${name}${args} on ${locations}`);
116
- }
117
- }
118
- if (directiveSchemas.length > 0) {
119
- const directivesPath = resolve(nitro.graphql.serverDir, "_directives.graphql");
120
- const content = `# WARNING: This file is auto-generated by nitro-graphql
121
- # Do not modify this file directly. It will be overwritten.
122
- # To define custom directives, create .directive.ts files using defineDirective()
123
-
124
- ${directiveSchemas.join("\n\n")}`;
125
- let shouldWrite = true;
126
- if (existsSync(directivesPath)) {
127
- const existingContent = readFileSync(directivesPath, "utf-8");
128
- shouldWrite = existingContent !== content;
129
- }
130
- if (shouldWrite) writeFileSync(directivesPath, content, "utf-8");
131
- if (!nitro.scanSchemas.includes(directivesPath)) nitro.scanSchemas.push(directivesPath);
132
- }
133
- }
84
+ await generateDirectiveSchemas(nitro, directives);
134
85
  nitro.hooks.hook("dev:start", async () => {
135
86
  const schemas$1 = await scanSchemas(nitro);
136
87
  nitro.scanSchemas = schemas$1;
@@ -138,56 +89,7 @@ ${directiveSchemas.join("\n\n")}`;
138
89
  nitro.scanResolvers = resolvers$1;
139
90
  const directives$1 = await scanDirectives(nitro);
140
91
  nitro.scanDirectives = directives$1;
141
- if (directives$1.length > 0) {
142
- const directiveSchemas = [];
143
- for (const dir of directives$1) for (const _imp of dir.imports) {
144
- const fileContent = await readFile(dir.specifier, "utf-8");
145
- const nameMatch = fileContent.match(/name:\s*['"`](\w+)['"`]/);
146
- const locationsMatch = fileContent.match(/locations:\s*\[([\s\S]*?)\]/);
147
- const argsMatch = fileContent.match(/args:\s*\{([\s\S]*?)\}\s*,\s*(?:description|transformer)/);
148
- if (nameMatch && locationsMatch) {
149
- const name = nameMatch[1];
150
- const locations = locationsMatch?.[1]?.split(",").map((l) => l.trim().replace(/['"`]/g, "")).filter(Boolean).join(" | ") || "";
151
- let args = "";
152
- if (argsMatch && argsMatch[1] && argsMatch[1].trim()) {
153
- const argDefs = [];
154
- const argMatches = argsMatch[1].matchAll(/(\w+):\s*\{([^}]+)\}/g);
155
- for (const argMatch of argMatches) {
156
- const argName = argMatch[1];
157
- const argBody = argMatch[2];
158
- const typeMatch = argBody?.match(/type:\s*['"`](\[?\w+!?\]?)['"`]/);
159
- const type = typeMatch ? typeMatch[1] : "String";
160
- const defaultMatch = argBody?.match(/defaultValue:\s*(['"`]([^'"`]+)['"`]|(\d+)|true|false)/);
161
- let defaultValue = "";
162
- if (defaultMatch) {
163
- const value = defaultMatch[2] || defaultMatch[3] || defaultMatch[1]?.replace(/['"`]/g, "");
164
- if (type === "String") defaultValue = ` = "${value}"`;
165
- else if (type === "Int" || type === "Float") defaultValue = ` = ${value}`;
166
- else if (type === "Boolean") defaultValue = ` = ${value}`;
167
- }
168
- argDefs.push(`${argName}: ${type}${defaultValue}`);
169
- }
170
- if (argDefs.length > 0) args = `(${argDefs.join(", ")})`;
171
- }
172
- directiveSchemas.push(`directive @${name}${args} on ${locations}`);
173
- }
174
- }
175
- if (directiveSchemas.length > 0) {
176
- const directivesPath = resolve(nitro.graphql.serverDir, "_directives.graphql");
177
- const content = `# WARNING: This file is auto-generated by nitro-graphql
178
- # Do not modify this file directly. It will be overwritten.
179
- # To define custom directives, create .directive.ts files using defineDirective()
180
-
181
- ${directiveSchemas.join("\n\n")}`;
182
- let shouldWrite = true;
183
- if (existsSync(directivesPath)) {
184
- const existingContent = readFileSync(directivesPath, "utf-8");
185
- shouldWrite = existingContent !== content;
186
- }
187
- if (shouldWrite) writeFileSync(directivesPath, content, "utf-8");
188
- if (!nitro.scanSchemas.includes(directivesPath)) nitro.scanSchemas.push(directivesPath);
189
- }
190
- }
92
+ await generateDirectiveSchemas(nitro, directives$1);
191
93
  const docs$1 = await scanDocs(nitro);
192
94
  nitro.scanDocuments = docs$1;
193
95
  });
package/dist/rollup.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { getImportId, scanGraphql } from "./utils/index.js";
2
2
  import { clientTypeGeneration, serverTypeGeneration } from "./utils/type-generation.js";
3
- import { readFile } from "node:fs/promises";
4
3
  import { resolve } from "pathe";
4
+ import { readFile } from "node:fs/promises";
5
5
  import { parse } from "graphql";
6
6
  import { genImport } from "knitwork";
7
7
 
@@ -1,6 +1,6 @@
1
- import * as h36 from "h3";
1
+ import * as h34 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: h34.EventHandler<h34.EventHandlerRequest, any>;
5
5
  //#endregion
6
6
  export { _default as default };
@@ -1,6 +1,6 @@
1
- import * as h31 from "h3";
1
+ import * as h36 from "h3";
2
2
 
3
3
  //#region src/routes/graphql-yoga.d.ts
4
- declare const _default: h31.EventHandler<h31.EventHandlerRequest, Promise<Response>>;
4
+ declare const _default: h36.EventHandler<h36.EventHandlerRequest, Promise<Response>>;
5
5
  //#endregion
6
6
  export { _default as default };
@@ -1,7 +1,7 @@
1
- import * as h34 from "h3";
1
+ import * as h32 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: h32.EventHandler<h32.EventHandlerRequest, Promise<{
5
5
  status: string;
6
6
  message: string;
7
7
  timestamp: string;
@@ -0,0 +1,80 @@
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 };
@@ -0,0 +1,233 @@
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
+ case "defaultValue":
157
+ defaultValue = this.extractLiteralValue(prop.value);
158
+ break;
159
+ }
160
+ }
161
+ return {
162
+ type,
163
+ ...defaultValue !== void 0 && { defaultValue }
164
+ };
165
+ }
166
+ /**
167
+ * Extract literal value (string, number, boolean)
168
+ */
169
+ extractLiteralValue(node) {
170
+ if (node?.type === "Literal") return node.value;
171
+ return void 0;
172
+ }
173
+ };
174
+ /**
175
+ * Generate GraphQL directive schema from parsed directive
176
+ */
177
+ function generateDirectiveSchema(directive) {
178
+ let args = "";
179
+ if (directive.args && Object.keys(directive.args).length > 0) {
180
+ const argDefs = Object.entries(directive.args).map(([name, arg]) => {
181
+ let defaultValue = "";
182
+ if (arg.defaultValue !== void 0) if (typeof arg.defaultValue === "string") defaultValue = ` = "${arg.defaultValue}"`;
183
+ else defaultValue = ` = ${arg.defaultValue}`;
184
+ return `${name}: ${arg.type}${defaultValue}`;
185
+ });
186
+ args = `(${argDefs.join(", ")})`;
187
+ }
188
+ const locations = directive.locations.join(" | ");
189
+ return `directive @${directive.name}${args} on ${locations}`;
190
+ }
191
+ /**
192
+ * Generate directive schemas file from scanned directives
193
+ */
194
+ async function generateDirectiveSchemas(nitro, directives) {
195
+ if (directives.length === 0) return;
196
+ const { existsSync, readFileSync, writeFileSync } = await import("node:fs");
197
+ const { readFile } = await import("node:fs/promises");
198
+ const { resolve } = await import("pathe");
199
+ const directiveSchemas = [];
200
+ const seenDirectives = /* @__PURE__ */ new Set();
201
+ for (const dir of directives) for (const _imp of dir.imports) {
202
+ const fileContent = await readFile(dir.specifier, "utf-8");
203
+ const directiveDefs = await directiveParser.parseDirectives(fileContent, dir.specifier);
204
+ for (const def of directiveDefs) {
205
+ if (seenDirectives.has(def.name)) continue;
206
+ seenDirectives.add(def.name);
207
+ const schema = generateDirectiveSchema(def);
208
+ directiveSchemas.push(schema);
209
+ }
210
+ }
211
+ if (directiveSchemas.length > 0) {
212
+ const directivesPath = resolve(nitro.graphql.serverDir, "_directives.graphql");
213
+ const content = `# WARNING: This file is auto-generated by nitro-graphql
214
+ # Do not modify this file directly. It will be overwritten.
215
+ # To define custom directives, create .directive.ts files using defineDirective()
216
+
217
+ ${directiveSchemas.join("\n\n")}`;
218
+ let shouldWrite = true;
219
+ if (existsSync(directivesPath)) {
220
+ const existingContent = readFileSync(directivesPath, "utf-8");
221
+ shouldWrite = existingContent !== content;
222
+ }
223
+ if (shouldWrite) writeFileSync(directivesPath, content, "utf-8");
224
+ if (!nitro.scanSchemas.includes(directivesPath)) nitro.scanSchemas.push(directivesPath);
225
+ }
226
+ }
227
+ /**
228
+ * Singleton instance for reuse
229
+ */
230
+ const directiveParser = new DirectiveParser();
231
+
232
+ //#endregion
233
+ export { DirectiveParser, directiveParser, generateDirectiveSchema, generateDirectiveSchemas };
@@ -1,4 +1,5 @@
1
1
  import { GenImport } from "../types/index.js";
2
+ import { directiveParser, generateDirectiveSchema, generateDirectiveSchemas } from "./directive-parser.js";
2
3
  import { Nitro } from "nitropack";
3
4
 
4
5
  //#region src/utils/index.d.ts
@@ -12,4 +13,4 @@ declare function scanTypeDefs(nitro: Nitro): Promise<string[]>;
12
13
  declare function scanSchemas(nitro: Nitro): Promise<string[]>;
13
14
  declare function scanDocs(nitro: Nitro): Promise<string[]>;
14
15
  //#endregion
15
- export { GLOB_SCAN_PATTERN, getImportId, relativeWithDot, scanDirectives, scanDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs };
16
+ export { GLOB_SCAN_PATTERN, directiveParser, generateDirectiveSchema, generateDirectiveSchemas, getImportId, relativeWithDot, scanDirectives, scanDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs };
@@ -1,5 +1,6 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { directiveParser, generateDirectiveSchema, generateDirectiveSchemas } from "./directive-parser.js";
2
2
  import { join, relative } from "pathe";
3
+ import { readFile } from "node:fs/promises";
3
4
  import { hash } from "ohash";
4
5
  import { parseAsync } from "oxc-parser";
5
6
  import { glob } from "tinyglobby";
@@ -131,4 +132,4 @@ async function scanDir(nitro, dir, name, globPattern = GLOB_SCAN_PATTERN) {
131
132
  }
132
133
 
133
134
  //#endregion
134
- export { GLOB_SCAN_PATTERN, getImportId, relativeWithDot, scanDirectives, scanDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs };
135
+ export { GLOB_SCAN_PATTERN, directiveParser, generateDirectiveSchema, generateDirectiveSchemas, getImportId, relativeWithDot, scanDirectives, scanDocs, scanGraphql, scanResolvers, scanSchemas, scanTypeDefs };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nitro-graphql",
3
3
  "type": "module",
4
- "version": "1.1.2",
4
+ "version": "1.1.3",
5
5
  "description": "GraphQL integration for Nitro",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,