@gqlkit-ts/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/auto-type-generator/auto-type-generator.d.ts +10 -4
  2. package/dist/auto-type-generator/auto-type-generator.d.ts.map +1 -1
  3. package/dist/auto-type-generator/auto-type-generator.js +640 -133
  4. package/dist/auto-type-generator/auto-type-generator.js.map +1 -1
  5. package/dist/auto-type-generator/index.d.ts +8 -1
  6. package/dist/auto-type-generator/index.d.ts.map +1 -1
  7. package/dist/auto-type-generator/index.js +3 -0
  8. package/dist/auto-type-generator/index.js.map +1 -1
  9. package/dist/auto-type-generator/inline-enum-collector.d.ts +13 -5
  10. package/dist/auto-type-generator/inline-enum-collector.d.ts.map +1 -1
  11. package/dist/auto-type-generator/inline-enum-collector.js +107 -71
  12. package/dist/auto-type-generator/inline-enum-collector.js.map +1 -1
  13. package/dist/auto-type-generator/inline-object-traverser.d.ts +20 -0
  14. package/dist/auto-type-generator/inline-object-traverser.d.ts.map +1 -0
  15. package/dist/auto-type-generator/inline-object-traverser.js +22 -0
  16. package/dist/auto-type-generator/inline-object-traverser.js.map +1 -0
  17. package/dist/auto-type-generator/inline-union-collector.d.ts +29 -0
  18. package/dist/auto-type-generator/inline-union-collector.d.ts.map +1 -0
  19. package/dist/auto-type-generator/inline-union-collector.js +216 -0
  20. package/dist/auto-type-generator/inline-union-collector.js.map +1 -0
  21. package/dist/auto-type-generator/inline-union-types.d.ts +29 -0
  22. package/dist/auto-type-generator/inline-union-types.d.ts.map +1 -0
  23. package/dist/auto-type-generator/inline-union-types.js +2 -0
  24. package/dist/auto-type-generator/inline-union-types.js.map +1 -0
  25. package/dist/auto-type-generator/inline-union-validator.d.ts +76 -0
  26. package/dist/auto-type-generator/inline-union-validator.d.ts.map +1 -0
  27. package/dist/auto-type-generator/inline-union-validator.js +329 -0
  28. package/dist/auto-type-generator/inline-union-validator.js.map +1 -0
  29. package/dist/auto-type-generator/naming-convention.d.ts +18 -1
  30. package/dist/auto-type-generator/naming-convention.d.ts.map +1 -1
  31. package/dist/auto-type-generator/naming-convention.js +16 -0
  32. package/dist/auto-type-generator/naming-convention.js.map +1 -1
  33. package/dist/auto-type-generator/resolve-type-generator.d.ts +20 -0
  34. package/dist/auto-type-generator/resolve-type-generator.d.ts.map +1 -0
  35. package/dist/auto-type-generator/resolve-type-generator.js +2 -0
  36. package/dist/auto-type-generator/resolve-type-generator.js.map +1 -0
  37. package/dist/auto-type-generator/resolver-field-iterator.d.ts +13 -0
  38. package/dist/auto-type-generator/resolver-field-iterator.d.ts.map +1 -0
  39. package/dist/auto-type-generator/resolver-field-iterator.js +22 -0
  40. package/dist/auto-type-generator/resolver-field-iterator.js.map +1 -0
  41. package/dist/auto-type-generator/typename-extractor.d.ts +26 -0
  42. package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -0
  43. package/dist/auto-type-generator/typename-extractor.js +142 -0
  44. package/dist/auto-type-generator/typename-extractor.js.map +1 -0
  45. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +35 -0
  46. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -0
  47. package/dist/auto-type-generator/typename-resolve-type-generator.js +177 -0
  48. package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -0
  49. package/dist/auto-type-generator/typename-types.d.ts +43 -0
  50. package/dist/auto-type-generator/typename-types.d.ts.map +1 -0
  51. package/dist/auto-type-generator/typename-types.js +37 -0
  52. package/dist/auto-type-generator/typename-types.js.map +1 -0
  53. package/dist/auto-type-generator/typename-validator.d.ts +37 -0
  54. package/dist/auto-type-generator/typename-validator.d.ts.map +1 -0
  55. package/dist/auto-type-generator/typename-validator.js +206 -0
  56. package/dist/auto-type-generator/typename-validator.js.map +1 -0
  57. package/dist/cli.js +2 -0
  58. package/dist/cli.js.map +1 -1
  59. package/dist/commands/docs.d.ts +51 -0
  60. package/dist/commands/docs.d.ts.map +1 -0
  61. package/dist/commands/docs.js +154 -0
  62. package/dist/commands/docs.js.map +1 -0
  63. package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
  64. package/dist/gen-orchestrator/orchestrator.js +13 -6
  65. package/dist/gen-orchestrator/orchestrator.js.map +1 -1
  66. package/dist/resolver-extractor/extract-resolvers.d.ts +19 -1
  67. package/dist/resolver-extractor/extract-resolvers.d.ts.map +1 -1
  68. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts +5 -0
  69. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
  70. package/dist/resolver-extractor/extractor/define-api-extractor.js +14 -61
  71. package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
  72. package/dist/resolver-extractor/index.d.ts +0 -1
  73. package/dist/resolver-extractor/index.d.ts.map +1 -1
  74. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +1 -0
  75. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts.map +1 -1
  76. package/dist/resolver-extractor/validator/abstract-resolver-validator.js +9 -5
  77. package/dist/resolver-extractor/validator/abstract-resolver-validator.js.map +1 -1
  78. package/dist/schema-generator/emitter/code-emitter.d.ts.map +1 -1
  79. package/dist/schema-generator/emitter/code-emitter.js +20 -0
  80. package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
  81. package/dist/schema-generator/generate-schema.d.ts +1 -0
  82. package/dist/schema-generator/generate-schema.d.ts.map +1 -1
  83. package/dist/schema-generator/generate-schema.js +72 -3
  84. package/dist/schema-generator/generate-schema.js.map +1 -1
  85. package/dist/schema-generator/integrator/result-integrator.d.ts +14 -2
  86. package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
  87. package/dist/schema-generator/integrator/result-integrator.js +54 -1
  88. package/dist/schema-generator/integrator/result-integrator.js.map +1 -1
  89. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts +2 -0
  90. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts.map +1 -1
  91. package/dist/schema-generator/resolver-collector/resolver-collector.js +22 -0
  92. package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
  93. package/dist/shared/enum-prefix-detector.d.ts +63 -0
  94. package/dist/shared/enum-prefix-detector.d.ts.map +1 -0
  95. package/dist/shared/enum-prefix-detector.js +80 -0
  96. package/dist/shared/enum-prefix-detector.js.map +1 -0
  97. package/dist/shared/ignore-fields-detector.d.ts +26 -0
  98. package/dist/shared/ignore-fields-detector.d.ts.map +1 -0
  99. package/dist/shared/ignore-fields-detector.js +83 -0
  100. package/dist/shared/ignore-fields-detector.js.map +1 -0
  101. package/dist/shared/ignore-fields-validator.d.ts +29 -0
  102. package/dist/shared/ignore-fields-validator.d.ts.map +1 -0
  103. package/dist/shared/ignore-fields-validator.js +43 -0
  104. package/dist/shared/ignore-fields-validator.js.map +1 -0
  105. package/dist/shared/index.d.ts +2 -0
  106. package/dist/shared/index.d.ts.map +1 -1
  107. package/dist/shared/index.js.map +1 -1
  108. package/dist/shared/source-location.d.ts +5 -0
  109. package/dist/shared/source-location.d.ts.map +1 -1
  110. package/dist/shared/source-location.js +7 -0
  111. package/dist/shared/source-location.js.map +1 -1
  112. package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
  113. package/dist/type-extractor/converter/graphql-converter.js +21 -7
  114. package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
  115. package/dist/type-extractor/extractor/field-type-resolver.js +42 -3
  116. package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -1
  117. package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
  118. package/dist/type-extractor/extractor/type-extractor.js +88 -23
  119. package/dist/type-extractor/extractor/type-extractor.js.map +1 -1
  120. package/dist/type-extractor/types/diagnostics.d.ts +1 -1
  121. package/dist/type-extractor/types/diagnostics.d.ts.map +1 -1
  122. package/dist/type-extractor/types/ts-type-reference-factory.d.ts +10 -2
  123. package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -1
  124. package/dist/type-extractor/types/ts-type-reference-factory.js +8 -2
  125. package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -1
  126. package/dist/type-extractor/types/typescript.d.ts +4 -0
  127. package/dist/type-extractor/types/typescript.d.ts.map +1 -1
  128. package/docs/coding-agents.md +64 -0
  129. package/docs/configuration.md +6 -20
  130. package/docs/getting-started.md +15 -12
  131. package/docs/index.md +36 -22
  132. package/docs/integration/apollo.md +8 -40
  133. package/docs/integration/drizzle.md +6 -10
  134. package/docs/integration/prisma.md +196 -0
  135. package/docs/integration/yoga.md +8 -40
  136. package/docs/schema/abstract-resolvers.md +117 -0
  137. package/docs/schema/directives.md +5 -0
  138. package/docs/schema/documentation.md +5 -0
  139. package/docs/schema/enums.md +99 -0
  140. package/docs/schema/fields.md +64 -0
  141. package/docs/schema/index.md +21 -0
  142. package/docs/schema/inputs.md +115 -15
  143. package/docs/schema/interfaces.md +31 -1
  144. package/docs/schema/objects.md +40 -0
  145. package/docs/schema/queries-mutations.md +136 -22
  146. package/docs/schema/scalars.md +5 -0
  147. package/docs/schema/unions.md +208 -1
  148. package/docs/what-is-gqlkit.md +13 -8
  149. package/package.json +6 -4
  150. package/src/auto-type-generator/auto-type-generator.ts +946 -201
  151. package/src/auto-type-generator/index.ts +42 -0
  152. package/src/auto-type-generator/inline-enum-collector.ts +187 -139
  153. package/src/auto-type-generator/inline-object-traverser.ts +49 -0
  154. package/src/auto-type-generator/inline-union-collector.ts +402 -0
  155. package/src/auto-type-generator/inline-union-types.ts +33 -0
  156. package/src/auto-type-generator/inline-union-validator.ts +482 -0
  157. package/src/auto-type-generator/naming-convention.ts +38 -1
  158. package/src/auto-type-generator/resolve-type-generator.ts +21 -0
  159. package/src/auto-type-generator/resolver-field-iterator.ts +39 -0
  160. package/src/auto-type-generator/typename-extractor.ts +230 -0
  161. package/src/auto-type-generator/typename-resolve-type-generator.ts +281 -0
  162. package/src/auto-type-generator/typename-types.ts +66 -0
  163. package/src/auto-type-generator/typename-validator.ts +326 -0
  164. package/src/cli.ts +2 -0
  165. package/src/commands/docs.ts +211 -0
  166. package/src/gen-orchestrator/orchestrator.ts +20 -6
  167. package/src/resolver-extractor/extract-resolvers.ts +19 -0
  168. package/src/resolver-extractor/extractor/define-api-extractor.ts +23 -89
  169. package/src/resolver-extractor/index.ts +0 -6
  170. package/src/resolver-extractor/validator/abstract-resolver-validator.ts +16 -8
  171. package/src/schema-generator/emitter/code-emitter.ts +34 -0
  172. package/src/schema-generator/generate-schema.ts +99 -2
  173. package/src/schema-generator/integrator/result-integrator.ts +70 -1
  174. package/src/schema-generator/resolver-collector/resolver-collector.ts +34 -0
  175. package/src/shared/enum-prefix-detector.ts +99 -0
  176. package/src/shared/ignore-fields-detector.ts +109 -0
  177. package/src/shared/ignore-fields-validator.ts +66 -0
  178. package/src/shared/index.ts +2 -0
  179. package/src/shared/source-location.ts +11 -0
  180. package/src/type-extractor/converter/graphql-converter.ts +31 -7
  181. package/src/type-extractor/extractor/field-type-resolver.ts +48 -3
  182. package/src/type-extractor/extractor/type-extractor.ts +103 -26
  183. package/src/type-extractor/types/diagnostics.ts +12 -2
  184. package/src/type-extractor/types/ts-type-reference-factory.ts +18 -5
  185. package/src/type-extractor/types/typescript.ts +4 -0
  186. package/dist/resolver-extractor/validator/only-validator.d.ts +0 -61
  187. package/dist/resolver-extractor/validator/only-validator.d.ts.map +0 -1
  188. package/dist/resolver-extractor/validator/only-validator.js +0 -76
  189. package/dist/resolver-extractor/validator/only-validator.js.map +0 -1
  190. package/src/resolver-extractor/validator/only-validator.ts +0 -158
@@ -0,0 +1,482 @@
1
+ import type {
2
+ Diagnostic,
3
+ ExtractedTypeInfo,
4
+ SourceLocation,
5
+ TSTypeReference,
6
+ } from "../type-extractor/types/index.js";
7
+ import type { InlineUnionMemberInfo } from "./inline-union-types.js";
8
+ import { isInputTypeName } from "./naming-convention.js";
9
+ import {
10
+ findTypenameProperty,
11
+ type TypenameFieldName,
12
+ } from "./typename-types.js";
13
+
14
+ export interface ValidateUnionResult {
15
+ readonly valid: boolean;
16
+ readonly diagnostics: ReadonlyArray<Diagnostic>;
17
+ }
18
+
19
+ export interface ValidateUnionMembersParams {
20
+ readonly members: ReadonlyArray<InlineUnionMemberInfo>;
21
+ readonly typeName: string;
22
+ readonly sourceLocation: SourceLocation;
23
+ readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
24
+ }
25
+
26
+ /**
27
+ * Validates that all union members are object types.
28
+ * GraphQL unions cannot contain primitives, enums, or scalars.
29
+ *
30
+ * Reports:
31
+ * - INLINE_UNION_PRIMITIVE_MEMBER for primitive types
32
+ * - INLINE_UNION_ENUM_MEMBER for enum types
33
+ * - INLINE_UNION_UNRESOLVABLE_MEMBER for unresolvable types
34
+ */
35
+ export function validateUnionMembers(
36
+ params: ValidateUnionMembersParams,
37
+ ): ValidateUnionResult {
38
+ const { members, typeName, sourceLocation, typeMap } = params;
39
+ const diagnostics: Diagnostic[] = [];
40
+
41
+ for (const member of members) {
42
+ const memberType = member.memberType;
43
+ const diagnostic = validateUnionMemberType({
44
+ memberType,
45
+ needsAutoGeneration: member.needsAutoGeneration,
46
+ typeName,
47
+ sourceLocation,
48
+ typeMap,
49
+ });
50
+ if (diagnostic) {
51
+ diagnostics.push(diagnostic);
52
+ }
53
+ }
54
+
55
+ return {
56
+ valid: diagnostics.length === 0,
57
+ diagnostics,
58
+ };
59
+ }
60
+
61
+ interface ValidateMemberTypeParams {
62
+ readonly memberType: TSTypeReference;
63
+ readonly needsAutoGeneration: boolean;
64
+ readonly typeName: string;
65
+ readonly sourceLocation: SourceLocation;
66
+ readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
67
+ }
68
+
69
+ function validateUnionMemberType(
70
+ params: ValidateMemberTypeParams,
71
+ ): Diagnostic | null {
72
+ const { memberType, needsAutoGeneration, typeName, sourceLocation, typeMap } =
73
+ params;
74
+
75
+ if (memberType.kind === "primitive") {
76
+ return {
77
+ code: "INLINE_UNION_PRIMITIVE_MEMBER",
78
+ message: `Inline union '${typeName}' contains primitive type '${memberType.name}'. GraphQL unions can only contain object types.`,
79
+ severity: "error",
80
+ location: sourceLocation,
81
+ };
82
+ }
83
+
84
+ if (memberType.kind === "inlineEnum") {
85
+ return {
86
+ code: "INLINE_UNION_ENUM_MEMBER",
87
+ message: `Inline union '${typeName}' contains an enum type. GraphQL unions can only contain object types.`,
88
+ severity: "error",
89
+ location: sourceLocation,
90
+ };
91
+ }
92
+
93
+ if (memberType.kind === "literal") {
94
+ return {
95
+ code: "INLINE_UNION_ENUM_MEMBER",
96
+ message: `Inline union '${typeName}' contains a literal type '${memberType.name}'. GraphQL unions can only contain object types.`,
97
+ severity: "error",
98
+ location: sourceLocation,
99
+ };
100
+ }
101
+
102
+ if (memberType.kind === "scalar") {
103
+ return {
104
+ code: "INLINE_UNION_UNRESOLVABLE_MEMBER",
105
+ message: `Inline union '${typeName}' contains scalar type '${memberType.scalarInfo?.scalarName ?? memberType.name}'. GraphQL unions can only contain object types.`,
106
+ severity: "error",
107
+ location: sourceLocation,
108
+ };
109
+ }
110
+
111
+ if (memberType.kind === "reference" && memberType.name !== null) {
112
+ const referencedType = typeMap.get(memberType.name);
113
+
114
+ if (referencedType?.metadata.kind === "enum") {
115
+ return {
116
+ code: "INLINE_UNION_ENUM_MEMBER",
117
+ message: `Inline union '${typeName}' contains enum type '${memberType.name}'. GraphQL unions can only contain object types.`,
118
+ severity: "error",
119
+ location: sourceLocation,
120
+ };
121
+ }
122
+
123
+ if (!referencedType && needsAutoGeneration) {
124
+ return {
125
+ code: "INLINE_UNION_UNRESOLVABLE_MEMBER",
126
+ message: `Inline union '${typeName}' contains unresolvable type '${memberType.name}'. The type cannot be expanded as an object type.`,
127
+ severity: "error",
128
+ location: sourceLocation,
129
+ };
130
+ }
131
+ }
132
+
133
+ if (
134
+ memberType.kind === "inlineObject" ||
135
+ memberType.kind === "reference" ||
136
+ memberType.kind === "union"
137
+ ) {
138
+ return null;
139
+ }
140
+
141
+ return {
142
+ code: "INLINE_UNION_UNRESOLVABLE_MEMBER",
143
+ message: `Inline union '${typeName}' contains a type that cannot be resolved as an object type.`,
144
+ severity: "error",
145
+ location: sourceLocation,
146
+ };
147
+ }
148
+
149
+ export interface ValidateOneOfMembersParams {
150
+ readonly members: ReadonlyArray<InlineUnionMemberInfo>;
151
+ readonly typeName: string;
152
+ readonly sourceLocation: SourceLocation;
153
+ readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
154
+ }
155
+
156
+ /**
157
+ * Validates @oneOf input object constraints:
158
+ * - Each member must have exactly one property
159
+ * - Property names must be unique across all members
160
+ * - Field types must be scalar, enum, or input object
161
+ *
162
+ * Reports:
163
+ * - ONEOF_EMPTY_OBJECT for empty object members
164
+ * - ONEOF_MULTIPLE_PROPERTIES for members with multiple properties
165
+ * - ONEOF_DUPLICATE_PROPERTY for duplicate property names
166
+ * - ONEOF_INVALID_FIELD_TYPE for invalid field types
167
+ */
168
+ export function validateOneOfMembers(
169
+ params: ValidateOneOfMembersParams,
170
+ ): ValidateUnionResult {
171
+ const { members, typeName, sourceLocation, typeMap } = params;
172
+ const diagnostics: Diagnostic[] = [];
173
+ const seenPropertyNames = new Set<string>();
174
+
175
+ for (let i = 0; i < members.length; i++) {
176
+ const member = members[i]!;
177
+ const memberType = member.memberType;
178
+
179
+ if (memberType.kind === "reference") {
180
+ continue;
181
+ }
182
+
183
+ if (
184
+ memberType.kind !== "inlineObject" ||
185
+ !memberType.inlineObjectProperties
186
+ ) {
187
+ continue;
188
+ }
189
+
190
+ const properties = memberType.inlineObjectProperties;
191
+
192
+ if (properties.length === 0) {
193
+ diagnostics.push({
194
+ code: "ONEOF_EMPTY_OBJECT",
195
+ message: `OneOf input '${typeName}' member at index ${i} is an empty object. Each member must have exactly one property.`,
196
+ severity: "error",
197
+ location: sourceLocation,
198
+ });
199
+ continue;
200
+ }
201
+
202
+ if (properties.length > 1) {
203
+ diagnostics.push({
204
+ code: "ONEOF_MULTIPLE_PROPERTIES",
205
+ message: `OneOf input '${typeName}' member at index ${i} has ${properties.length} properties. Each member must have exactly one property.`,
206
+ severity: "error",
207
+ location: sourceLocation,
208
+ });
209
+ continue;
210
+ }
211
+
212
+ const prop = properties[0]!;
213
+
214
+ if (seenPropertyNames.has(prop.name)) {
215
+ diagnostics.push({
216
+ code: "ONEOF_DUPLICATE_PROPERTY",
217
+ message: `OneOf input '${typeName}' has duplicate property name '${prop.name}'.`,
218
+ severity: "error",
219
+ location: sourceLocation,
220
+ });
221
+ continue;
222
+ }
223
+ seenPropertyNames.add(prop.name);
224
+
225
+ const fieldTypeError = validateOneOfFieldType({
226
+ propertyName: prop.name,
227
+ propertyType: prop.tsType,
228
+ typeName,
229
+ sourceLocation,
230
+ typeMap,
231
+ });
232
+ if (fieldTypeError) {
233
+ diagnostics.push(fieldTypeError);
234
+ }
235
+ }
236
+
237
+ return {
238
+ valid: diagnostics.length === 0,
239
+ diagnostics,
240
+ };
241
+ }
242
+
243
+ interface ValidateOneOfFieldTypeParams {
244
+ readonly propertyName: string;
245
+ readonly propertyType: TSTypeReference;
246
+ readonly typeName: string;
247
+ readonly sourceLocation: SourceLocation;
248
+ readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
249
+ }
250
+
251
+ function validateOneOfFieldType(
252
+ params: ValidateOneOfFieldTypeParams,
253
+ ): Diagnostic | null {
254
+ const { propertyName, propertyType, typeName, sourceLocation, typeMap } =
255
+ params;
256
+
257
+ if (
258
+ propertyType.kind === "primitive" ||
259
+ propertyType.kind === "scalar" ||
260
+ propertyType.kind === "inlineEnum"
261
+ ) {
262
+ return null;
263
+ }
264
+
265
+ if (propertyType.kind === "inlineObject") {
266
+ return null;
267
+ }
268
+
269
+ if (propertyType.kind === "reference" && propertyType.name !== null) {
270
+ const referencedType = typeMap.get(propertyType.name);
271
+
272
+ if (!referencedType) {
273
+ return null;
274
+ }
275
+
276
+ const isValidType =
277
+ referencedType.metadata.kind === "enum" ||
278
+ isInputTypeName(referencedType.metadata.name);
279
+
280
+ if (isValidType) {
281
+ return null;
282
+ }
283
+
284
+ return {
285
+ code: "ONEOF_INVALID_FIELD_TYPE",
286
+ message: `OneOf input '${typeName}' field '${propertyName}' has invalid type '${propertyType.name}'. Only scalar types, enum types, and Input Object types are allowed.`,
287
+ severity: "error",
288
+ location: sourceLocation,
289
+ };
290
+ }
291
+
292
+ return null;
293
+ }
294
+
295
+ export interface ValidateUnionMemberTypenamesParams {
296
+ readonly members: ReadonlyArray<InlineUnionMemberInfo>;
297
+ readonly unionTypeName: string;
298
+ readonly sourceLocation: SourceLocation;
299
+ readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
300
+ }
301
+
302
+ export interface ValidatedTypenameInfo {
303
+ readonly typeName: string;
304
+ readonly fieldName: TypenameFieldName;
305
+ }
306
+
307
+ interface ExtractTypenameFromNamedTypeParams {
308
+ readonly memberType: TSTypeReference;
309
+ readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
310
+ }
311
+
312
+ /**
313
+ * Extract typename info from a named type by looking up its fields in the typeMap.
314
+ * Returns null if the type doesn't have a valid __typename or $typeName field.
315
+ */
316
+ function extractTypenameFromNamedType(
317
+ params: ExtractTypenameFromNamedTypeParams,
318
+ ): ValidatedTypenameInfo | null {
319
+ const { memberType, typeMap } = params;
320
+
321
+ if (memberType.kind !== "reference" || memberType.name === null) {
322
+ return null;
323
+ }
324
+
325
+ const referencedType = typeMap.get(memberType.name);
326
+ if (!referencedType) {
327
+ return null;
328
+ }
329
+
330
+ const found = findTypenameProperty(referencedType.fields, (f) => f.name);
331
+ if (!found) {
332
+ return null;
333
+ }
334
+
335
+ const { property: field, fieldName } = found;
336
+ const { tsType } = field;
337
+
338
+ if (
339
+ field.optional ||
340
+ tsType.nullable ||
341
+ tsType.kind !== "literal" ||
342
+ tsType.name === null
343
+ ) {
344
+ return null;
345
+ }
346
+
347
+ return { typeName: tsType.name, fieldName };
348
+ }
349
+
350
+ export interface ValidateUnionMemberTypenamesResult {
351
+ readonly valid: boolean;
352
+ readonly diagnostics: ReadonlyArray<Diagnostic>;
353
+ readonly memberTypenames: ReadonlyMap<number, ValidatedTypenameInfo>;
354
+ readonly allMembersHaveTypename: boolean;
355
+ }
356
+
357
+ /**
358
+ * Validates __typename or $typeName property on inline union members.
359
+ * Returns extracted typename values for valid members.
360
+ *
361
+ * Behavior:
362
+ * - For named types (needsAutoGeneration: false), extracts typename from typeMap
363
+ * - For inline types (needsAutoGeneration: true), validates and extracts typename
364
+ * - Only called for payload unions (context.kind === "resolverPayload")
365
+ * - __typename takes priority over $typeName if both are present
366
+ *
367
+ * Reports:
368
+ * - MISSING_TYPENAME_PROPERTY when neither __typename nor $typeName is present
369
+ * - INVALID_TYPENAME_TYPE when the property is not a string literal
370
+ * - OPTIONAL_TYPENAME_PROPERTY when the property is declared as optional
371
+ * - NULLABLE_TYPENAME_PROPERTY when the property is nullable
372
+ */
373
+ export function validateUnionMemberTypenames(
374
+ params: ValidateUnionMemberTypenamesParams,
375
+ ): ValidateUnionMemberTypenamesResult {
376
+ const { members, unionTypeName, sourceLocation, typeMap } = params;
377
+ const diagnostics: Diagnostic[] = [];
378
+ const memberTypenames = new Map<number, ValidatedTypenameInfo>();
379
+
380
+ // Counters for determining allMembersHaveTypename
381
+ let inlineTypeCount = 0;
382
+ let inlineTypesWithTypename = 0;
383
+ let namedTypeCount = 0;
384
+ let namedTypesWithTypename = 0;
385
+
386
+ for (let i = 0; i < members.length; i++) {
387
+ const member = members[i]!;
388
+ const memberType = member.memberType;
389
+
390
+ if (!member.needsAutoGeneration) {
391
+ namedTypeCount++;
392
+ const typenameInfo = extractTypenameFromNamedType({
393
+ memberType,
394
+ typeMap,
395
+ });
396
+ if (typenameInfo) {
397
+ memberTypenames.set(i, typenameInfo);
398
+ namedTypesWithTypename++;
399
+ }
400
+ continue;
401
+ }
402
+
403
+ if (
404
+ memberType.kind !== "inlineObject" ||
405
+ !memberType.inlineObjectProperties
406
+ ) {
407
+ continue;
408
+ }
409
+
410
+ inlineTypeCount++;
411
+
412
+ const found = findTypenameProperty(
413
+ memberType.inlineObjectProperties,
414
+ (prop) => prop.name,
415
+ );
416
+
417
+ if (!found) {
418
+ diagnostics.push({
419
+ code: "MISSING_TYPENAME_PROPERTY",
420
+ message: `Union '${unionTypeName}' member at index ${i} is missing '__typename' or '$typeName' property. Inline union members must have a '__typename' or '$typeName' property with a string literal type.`,
421
+ severity: "error",
422
+ location: sourceLocation,
423
+ });
424
+ continue;
425
+ }
426
+
427
+ const { property: selectedProperty, fieldName: selectedFieldName } = found;
428
+ const typenameType = selectedProperty.tsType;
429
+
430
+ if (selectedProperty.optional) {
431
+ diagnostics.push({
432
+ code: "OPTIONAL_TYPENAME_PROPERTY",
433
+ message: `Union '${unionTypeName}' member at index ${i} has optional '${selectedFieldName}' property. The '${selectedFieldName}' property must be required for union type resolution.`,
434
+ severity: "error",
435
+ location: sourceLocation,
436
+ });
437
+ }
438
+
439
+ if (typenameType.nullable) {
440
+ diagnostics.push({
441
+ code: "NULLABLE_TYPENAME_PROPERTY",
442
+ message: `Union '${unionTypeName}' member at index ${i} has nullable '${selectedFieldName}' property. The '${selectedFieldName}' property must not be nullable for union type resolution.`,
443
+ severity: "error",
444
+ location: sourceLocation,
445
+ });
446
+ }
447
+
448
+ if (typenameType.kind !== "literal" || typenameType.name === null) {
449
+ diagnostics.push({
450
+ code: "INVALID_TYPENAME_TYPE",
451
+ message: `Union '${unionTypeName}' member at index ${i} has '${selectedFieldName}' that is not a string literal type. Expected a string literal like '${selectedFieldName}: "TypeName"'.`,
452
+ severity: "error",
453
+ location: sourceLocation,
454
+ });
455
+ continue;
456
+ }
457
+
458
+ if (!selectedProperty.optional && !typenameType.nullable) {
459
+ memberTypenames.set(i, {
460
+ typeName: typenameType.name,
461
+ fieldName: selectedFieldName,
462
+ });
463
+ inlineTypesWithTypename++;
464
+ }
465
+ }
466
+
467
+ // Determine allMembersHaveTypename: all members (both inline and named) must have typename
468
+ // for resolveType to be auto-generated. This ensures the generated resolveType function
469
+ // can resolve all union members correctly at runtime.
470
+ const totalMemberCount = inlineTypeCount + namedTypeCount;
471
+ const totalMembersWithTypename =
472
+ inlineTypesWithTypename + namedTypesWithTypename;
473
+ const allMembersHaveTypename =
474
+ totalMemberCount > 0 && totalMembersWithTypename === totalMemberCount;
475
+
476
+ return {
477
+ valid: diagnostics.length === 0,
478
+ diagnostics,
479
+ memberTypenames,
480
+ allMembersHaveTypename,
481
+ };
482
+ }
@@ -4,7 +4,8 @@
4
4
  export type AutoTypeNameContext =
5
5
  | ObjectFieldContext
6
6
  | InputFieldContext
7
- | ResolverArgContext;
7
+ | ResolverArgContext
8
+ | ResolverPayloadContext;
8
9
 
9
10
  /**
10
11
  * Context for Object type field inline objects.
@@ -40,6 +41,20 @@ export interface ResolverArgContext {
40
41
  readonly fieldPath: ReadonlyArray<string>;
41
42
  }
42
43
 
44
+ /**
45
+ * Context for resolver payload inline types.
46
+ * Query/Mutation: {PascalCaseFieldName}Payload
47
+ * Field resolver: {ParentTypeName}{PascalCaseFieldName}Payload
48
+ * Nested: {PayloadTypeName}{PascalCaseFieldPath} (no Input suffix)
49
+ */
50
+ export interface ResolverPayloadContext {
51
+ readonly kind: "resolverPayload";
52
+ readonly resolverType: "query" | "mutation" | "field";
53
+ readonly fieldName: string;
54
+ readonly parentTypeName: string | null;
55
+ readonly fieldPath: ReadonlyArray<string>;
56
+ }
57
+
43
58
  /**
44
59
  * Convert a string to PascalCase.
45
60
  * Handles camelCase, snake_case, and kebab-case inputs.
@@ -71,6 +86,13 @@ function removeInputSuffix(typeName: string): string {
71
86
  return typeName;
72
87
  }
73
88
 
89
+ /**
90
+ * Check if a type name follows the Input type naming convention.
91
+ */
92
+ export function isInputTypeName(name: string): boolean {
93
+ return name.endsWith("Input");
94
+ }
95
+
74
96
  /**
75
97
  * Build a field context (object or input) based on the parent type name.
76
98
  */
@@ -95,6 +117,8 @@ export function generateAutoTypeName(context: AutoTypeNameContext): string {
95
117
  return generateInputFieldTypeName(context);
96
118
  case "resolverArg":
97
119
  return generateResolverArgTypeName(context);
120
+ case "resolverPayload":
121
+ return generateResolverPayloadTypeName(context);
98
122
  }
99
123
  }
100
124
 
@@ -124,3 +148,16 @@ function generateResolverArgTypeName(context: ResolverArgContext): string {
124
148
 
125
149
  return `${fieldNamePascal}${argNamePascal}${pathParts}Input`;
126
150
  }
151
+
152
+ function generateResolverPayloadTypeName(
153
+ context: ResolverPayloadContext,
154
+ ): string {
155
+ const fieldNamePascal = toPascalCase(context.fieldName);
156
+ const pathParts = context.fieldPath.map(toPascalCase).join("");
157
+
158
+ if (context.resolverType === "field" && context.parentTypeName) {
159
+ return `${context.parentTypeName}${fieldNamePascal}Payload${pathParts}`;
160
+ }
161
+
162
+ return `${fieldNamePascal}Payload${pathParts}`;
163
+ }
@@ -0,0 +1,21 @@
1
+ import type { TypenameFieldNameSet } from "./typename-types.js";
2
+
3
+ /**
4
+ * Pattern describing which typename fields are used for resolving types.
5
+ * - Single field: Set contains one field name (e.g., {"__typename"} or {"$typeName"})
6
+ * - Mixed: Set contains multiple field names (e.g., {"__typename", "$typeName"})
7
+ */
8
+ export interface ResolveTypeFieldPattern {
9
+ readonly usedFieldNames: TypenameFieldNameSet;
10
+ }
11
+
12
+ /**
13
+ * Information about an auto-generated __resolveType function for a Union type.
14
+ * The generated function returns `obj.__typename` to resolve the concrete type.
15
+ */
16
+ export interface AutoGeneratedResolveType {
17
+ /** The name of the Union type */
18
+ readonly unionTypeName: string;
19
+ /** The field pattern used to resolve the type name */
20
+ readonly fieldPattern: ResolveTypeFieldPattern;
21
+ }
@@ -0,0 +1,39 @@
1
+ import type {
2
+ ExtractResolversResult,
3
+ GraphQLFieldDefinition,
4
+ } from "../resolver-extractor/index.js";
5
+
6
+ export type ResolverType = "query" | "mutation" | "field";
7
+
8
+ export interface ResolverFieldInfo {
9
+ readonly field: GraphQLFieldDefinition;
10
+ readonly resolverType: ResolverType;
11
+ readonly parentTypeName: string | null;
12
+ }
13
+
14
+ /**
15
+ * Iterates over all resolver fields (query, mutation, field extensions) in a consistent manner.
16
+ * This eliminates the repeated iteration pattern across multiple collector functions.
17
+ */
18
+ export function forEachResolverField(
19
+ resolversResult: ExtractResolversResult,
20
+ visitor: (info: ResolverFieldInfo) => void,
21
+ ): void {
22
+ for (const field of resolversResult.queryFields.fields) {
23
+ visitor({ field, resolverType: "query", parentTypeName: null });
24
+ }
25
+
26
+ for (const field of resolversResult.mutationFields.fields) {
27
+ visitor({ field, resolverType: "mutation", parentTypeName: null });
28
+ }
29
+
30
+ for (const ext of resolversResult.typeExtensions) {
31
+ for (const field of ext.fields) {
32
+ visitor({
33
+ field,
34
+ resolverType: "field",
35
+ parentTypeName: ext.targetTypeName,
36
+ });
37
+ }
38
+ }
39
+ }