@gqlkit-ts/cli 0.6.0 → 0.7.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 +7 -0
  2. package/dist/auto-type-generator/auto-type-generator.d.ts.map +1 -1
  3. package/dist/auto-type-generator/auto-type-generator.js +375 -55
  4. package/dist/auto-type-generator/auto-type-generator.js.map +1 -1
  5. package/dist/auto-type-generator/discriminator-field-validator.d.ts +26 -0
  6. package/dist/auto-type-generator/discriminator-field-validator.d.ts.map +1 -0
  7. package/dist/auto-type-generator/discriminator-field-validator.js +242 -0
  8. package/dist/auto-type-generator/discriminator-field-validator.js.map +1 -0
  9. package/dist/auto-type-generator/discriminator-naming.d.ts +11 -0
  10. package/dist/auto-type-generator/discriminator-naming.d.ts.map +1 -0
  11. package/dist/auto-type-generator/discriminator-naming.js +15 -0
  12. package/dist/auto-type-generator/discriminator-naming.js.map +1 -0
  13. package/dist/auto-type-generator/discriminator-resolve-type-generator.d.ts +44 -0
  14. package/dist/auto-type-generator/discriminator-resolve-type-generator.d.ts.map +1 -0
  15. package/dist/auto-type-generator/discriminator-resolve-type-generator.js +77 -0
  16. package/dist/auto-type-generator/discriminator-resolve-type-generator.js.map +1 -0
  17. package/dist/auto-type-generator/index.d.ts +3 -0
  18. package/dist/auto-type-generator/index.d.ts.map +1 -1
  19. package/dist/auto-type-generator/index.js +3 -0
  20. package/dist/auto-type-generator/index.js.map +1 -1
  21. package/dist/auto-type-generator/inline-enum-collector.d.ts.map +1 -1
  22. package/dist/auto-type-generator/inline-enum-collector.js +14 -7
  23. package/dist/auto-type-generator/inline-enum-collector.js.map +1 -1
  24. package/dist/auto-type-generator/inline-object-converter.d.ts +12 -0
  25. package/dist/auto-type-generator/inline-object-converter.d.ts.map +1 -0
  26. package/dist/auto-type-generator/inline-object-converter.js +72 -0
  27. package/dist/auto-type-generator/inline-object-converter.js.map +1 -0
  28. package/dist/auto-type-generator/inline-object-traverser.d.ts +2 -1
  29. package/dist/auto-type-generator/inline-object-traverser.d.ts.map +1 -1
  30. package/dist/auto-type-generator/inline-object-traverser.js +22 -4
  31. package/dist/auto-type-generator/inline-object-traverser.js.map +1 -1
  32. package/dist/auto-type-generator/inline-union-collector.d.ts.map +1 -1
  33. package/dist/auto-type-generator/inline-union-collector.js +20 -6
  34. package/dist/auto-type-generator/inline-union-collector.js.map +1 -1
  35. package/dist/auto-type-generator/inline-union-types.d.ts +2 -0
  36. package/dist/auto-type-generator/inline-union-types.d.ts.map +1 -1
  37. package/dist/auto-type-generator/inline-union-validator.js +3 -3
  38. package/dist/auto-type-generator/inline-union-validator.js.map +1 -1
  39. package/dist/auto-type-generator/intersection-flattener.d.ts +44 -0
  40. package/dist/auto-type-generator/intersection-flattener.d.ts.map +1 -0
  41. package/dist/auto-type-generator/intersection-flattener.js +398 -0
  42. package/dist/auto-type-generator/intersection-flattener.js.map +1 -0
  43. package/dist/auto-type-generator/naming-convention.d.ts +21 -0
  44. package/dist/auto-type-generator/naming-convention.d.ts.map +1 -1
  45. package/dist/auto-type-generator/naming-convention.js +145 -1
  46. package/dist/auto-type-generator/naming-convention.js.map +1 -1
  47. package/dist/auto-type-generator/typename-extractor.d.ts +2 -0
  48. package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -1
  49. package/dist/auto-type-generator/typename-extractor.js +11 -3
  50. package/dist/auto-type-generator/typename-extractor.js.map +1 -1
  51. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +2 -0
  52. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -1
  53. package/dist/auto-type-generator/typename-resolve-type-generator.js +12 -84
  54. package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -1
  55. package/dist/auto-type-generator/typename-types.d.ts +4 -0
  56. package/dist/auto-type-generator/typename-types.d.ts.map +1 -1
  57. package/dist/auto-type-generator/typename-types.js +6 -0
  58. package/dist/auto-type-generator/typename-types.js.map +1 -1
  59. package/dist/auto-type-generator/typename-validator.d.ts.map +1 -1
  60. package/dist/auto-type-generator/typename-validator.js +4 -3
  61. package/dist/auto-type-generator/typename-validator.js.map +1 -1
  62. package/dist/commands/gen.d.ts.map +1 -1
  63. package/dist/commands/gen.js +2 -1
  64. package/dist/commands/gen.js.map +1 -1
  65. package/dist/config/types.d.ts +7 -0
  66. package/dist/config/types.d.ts.map +1 -1
  67. package/dist/config-loader/index.d.ts +1 -1
  68. package/dist/config-loader/index.d.ts.map +1 -1
  69. package/dist/config-loader/index.js.map +1 -1
  70. package/dist/config-loader/loader.d.ts +6 -0
  71. package/dist/config-loader/loader.d.ts.map +1 -1
  72. package/dist/config-loader/loader.js +1 -0
  73. package/dist/config-loader/loader.js.map +1 -1
  74. package/dist/config-loader/validator.d.ts.map +1 -1
  75. package/dist/config-loader/validator.js +84 -1
  76. package/dist/config-loader/validator.js.map +1 -1
  77. package/dist/gen-orchestrator/orchestrator.d.ts +2 -1
  78. package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
  79. package/dist/gen-orchestrator/orchestrator.js +15 -2
  80. package/dist/gen-orchestrator/orchestrator.js.map +1 -1
  81. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
  82. package/dist/resolver-extractor/extractor/define-api-extractor.js +4 -0
  83. package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
  84. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +2 -0
  85. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts.map +1 -1
  86. package/dist/resolver-extractor/validator/abstract-resolver-validator.js +16 -3
  87. package/dist/resolver-extractor/validator/abstract-resolver-validator.js.map +1 -1
  88. package/dist/schema-generator/emitter/code-emitter.d.ts.map +1 -1
  89. package/dist/schema-generator/emitter/code-emitter.js +13 -1
  90. package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
  91. package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.d.ts +18 -0
  92. package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.d.ts.map +1 -0
  93. package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.js +89 -0
  94. package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.js.map +1 -0
  95. package/dist/schema-generator/generate-schema.d.ts +2 -0
  96. package/dist/schema-generator/generate-schema.d.ts.map +1 -1
  97. package/dist/schema-generator/generate-schema.js +69 -10
  98. package/dist/schema-generator/generate-schema.js.map +1 -1
  99. package/dist/schema-generator/integrator/result-integrator.d.ts +4 -0
  100. package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
  101. package/dist/schema-generator/integrator/result-integrator.js +18 -2
  102. package/dist/schema-generator/integrator/result-integrator.js.map +1 -1
  103. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts +2 -0
  104. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts.map +1 -1
  105. package/dist/schema-generator/resolver-collector/resolver-collector.js +4 -0
  106. package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
  107. package/dist/shared/constants.d.ts.map +1 -1
  108. package/dist/shared/constants.js +14 -1
  109. package/dist/shared/constants.js.map +1 -1
  110. package/dist/shared/enum-prefix-detector.d.ts.map +1 -1
  111. package/dist/shared/enum-prefix-detector.js +78 -8
  112. package/dist/shared/enum-prefix-detector.js.map +1 -1
  113. package/dist/shared/inline-object-utils.js +1 -1
  114. package/dist/shared/inline-object-utils.js.map +1 -1
  115. package/dist/shared/type-converter.d.ts.map +1 -1
  116. package/dist/shared/type-converter.js +55 -0
  117. package/dist/shared/type-converter.js.map +1 -1
  118. package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
  119. package/dist/type-extractor/converter/graphql-converter.js +11 -1
  120. package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
  121. package/dist/type-extractor/extractor/field-type-resolver.d.ts +18 -0
  122. package/dist/type-extractor/extractor/field-type-resolver.d.ts.map +1 -1
  123. package/dist/type-extractor/extractor/field-type-resolver.js +198 -15
  124. package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -1
  125. package/dist/type-extractor/extractor/type-extractor.d.ts +1 -0
  126. package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
  127. package/dist/type-extractor/extractor/type-extractor.js +100 -9
  128. package/dist/type-extractor/extractor/type-extractor.js.map +1 -1
  129. package/dist/type-extractor/types/diagnostics.d.ts +1 -1
  130. package/dist/type-extractor/types/diagnostics.d.ts.map +1 -1
  131. package/dist/type-extractor/types/index.d.ts +1 -1
  132. package/dist/type-extractor/types/index.d.ts.map +1 -1
  133. package/dist/type-extractor/types/index.js +1 -1
  134. package/dist/type-extractor/types/index.js.map +1 -1
  135. package/dist/type-extractor/types/ts-type-reference-factory.d.ts +7 -1
  136. package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -1
  137. package/dist/type-extractor/types/ts-type-reference-factory.js +18 -3
  138. package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -1
  139. package/dist/type-extractor/types/typescript.d.ts +3 -1
  140. package/dist/type-extractor/types/typescript.d.ts.map +1 -1
  141. package/dist/type-extractor/validator/type-validator.d.ts.map +1 -1
  142. package/dist/type-extractor/validator/type-validator.js +6 -1
  143. package/dist/type-extractor/validator/type-validator.js.map +1 -1
  144. package/docs/configuration.md +19 -0
  145. package/docs/index.md +1 -0
  146. package/docs/integration/ai-sdk.md +189 -0
  147. package/docs/schema/unions.md +117 -0
  148. package/package.json +2 -2
  149. package/src/auto-type-generator/auto-type-generator.ts +576 -58
  150. package/src/auto-type-generator/discriminator-field-validator.ts +368 -0
  151. package/src/auto-type-generator/discriminator-naming.ts +24 -0
  152. package/src/auto-type-generator/discriminator-resolve-type-generator.ts +136 -0
  153. package/src/auto-type-generator/index.ts +17 -0
  154. package/src/auto-type-generator/inline-enum-collector.ts +19 -4
  155. package/src/auto-type-generator/inline-object-converter.ts +100 -0
  156. package/src/auto-type-generator/inline-object-traverser.ts +33 -7
  157. package/src/auto-type-generator/inline-union-collector.ts +26 -4
  158. package/src/auto-type-generator/inline-union-types.ts +2 -0
  159. package/src/auto-type-generator/inline-union-validator.ts +3 -3
  160. package/src/auto-type-generator/intersection-flattener.ts +554 -0
  161. package/src/auto-type-generator/naming-convention.ts +205 -1
  162. package/src/auto-type-generator/typename-extractor.ts +17 -3
  163. package/src/auto-type-generator/typename-resolve-type-generator.ts +19 -108
  164. package/src/auto-type-generator/typename-types.ts +7 -0
  165. package/src/auto-type-generator/typename-validator.ts +4 -3
  166. package/src/commands/gen.ts +9 -2
  167. package/src/config/types.ts +10 -0
  168. package/src/config-loader/index.ts +1 -0
  169. package/src/config-loader/loader.ts +11 -0
  170. package/src/config-loader/validator.ts +100 -1
  171. package/src/gen-orchestrator/orchestrator.ts +19 -2
  172. package/src/resolver-extractor/extractor/define-api-extractor.ts +4 -0
  173. package/src/resolver-extractor/validator/abstract-resolver-validator.ts +20 -6
  174. package/src/schema-generator/emitter/code-emitter.ts +26 -1
  175. package/src/schema-generator/emitter/discriminator-resolve-type-emitter.ts +125 -0
  176. package/src/schema-generator/generate-schema.ts +100 -13
  177. package/src/schema-generator/integrator/result-integrator.ts +25 -1
  178. package/src/schema-generator/resolver-collector/resolver-collector.ts +7 -0
  179. package/src/shared/constants.ts +15 -1
  180. package/src/shared/enum-prefix-detector.ts +96 -8
  181. package/src/shared/inline-object-utils.ts +1 -1
  182. package/src/shared/type-converter.ts +63 -0
  183. package/src/type-extractor/converter/graphql-converter.ts +17 -1
  184. package/src/type-extractor/extractor/field-type-resolver.ts +241 -16
  185. package/src/type-extractor/extractor/type-extractor.ts +119 -5
  186. package/src/type-extractor/types/diagnostics.ts +10 -1
  187. package/src/type-extractor/types/index.ts +2 -1
  188. package/src/type-extractor/types/ts-type-reference-factory.ts +24 -3
  189. package/src/type-extractor/types/typescript.ts +6 -2
  190. package/src/type-extractor/validator/type-validator.ts +6 -1
@@ -0,0 +1,368 @@
1
+ import type { ResolvedDiscriminatorFieldsMap } from "../config-loader/index.js";
2
+ import type {
3
+ Diagnostic,
4
+ ExtractedTypeInfo,
5
+ FieldDefinition,
6
+ InlineObjectMember,
7
+ SourceLocation,
8
+ TSTypeReference,
9
+ } from "../type-extractor/types/index.js";
10
+ import type { ValidatedDiscriminatorEntry } from "./discriminator-resolve-type-generator.js";
11
+
12
+ export interface ValidateDiscriminatorFieldsParams {
13
+ readonly discriminatorFields: ResolvedDiscriminatorFieldsMap;
14
+ readonly extractedTypes: ReadonlyArray<ExtractedTypeInfo>;
15
+ readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
16
+ /** Union names already handled by inline union flattening (skip DISCRIMINATOR_UNKNOWN_UNION) */
17
+ readonly inlineDiscriminatorUnionNames: ReadonlySet<string>;
18
+ }
19
+
20
+ export interface ValidateDiscriminatorFieldsResult {
21
+ readonly diagnostics: ReadonlyArray<Diagnostic>;
22
+ readonly validatedEntries: ReadonlyArray<ValidatedDiscriminatorEntry>;
23
+ }
24
+
25
+ /** Identifier used for each member when collecting value tuples. */
26
+ interface MemberIdentifier {
27
+ readonly label: string;
28
+ }
29
+
30
+ function findFieldInNamedMember(
31
+ memberType: ExtractedTypeInfo,
32
+ fieldName: string,
33
+ ): FieldDefinition | null {
34
+ for (const field of memberType.fields) {
35
+ if (field.name === fieldName) {
36
+ return field;
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+
42
+ function findFieldInInlineMember(
43
+ inlineMember: InlineObjectMember,
44
+ fieldName: string,
45
+ ): TSTypeReference | null {
46
+ for (const prop of inlineMember.properties) {
47
+ if (prop.propertyName === fieldName) {
48
+ return prop.propertyType;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /** Extract a string literal value from a TSTypeReference, or null if not a string literal. */
55
+ function getStringLiteralValue(tsType: TSTypeReference): string | null {
56
+ if (tsType.kind === "stringLiteral" && tsType.name !== null) {
57
+ return tsType.name;
58
+ }
59
+ return null;
60
+ }
61
+
62
+ /**
63
+ * Validates primary (first) discriminator field for a named member.
64
+ * Returns the string literal value if valid, or null if an error was reported.
65
+ */
66
+ function validatePrimaryFieldForNamedMember(
67
+ memberType: ExtractedTypeInfo,
68
+ primaryFieldName: string,
69
+ unionTypeName: string,
70
+ memberName: string,
71
+ sourceLocation: SourceLocation,
72
+ diagnostics: Diagnostic[],
73
+ ): string | null {
74
+ const field = findFieldInNamedMember(memberType, primaryFieldName);
75
+ if (field === null) {
76
+ diagnostics.push({
77
+ code: "DISCRIMINATOR_FIELD_NOT_FOUND",
78
+ message: `Union '${unionTypeName}' member '${memberName}' does not have discriminator field '${primaryFieldName}'.`,
79
+ severity: "error",
80
+ location: sourceLocation,
81
+ });
82
+ return null;
83
+ }
84
+
85
+ const value = getStringLiteralValue(field.tsType);
86
+ if (value === null) {
87
+ diagnostics.push({
88
+ code: "DISCRIMINATOR_FIELD_NOT_STRING_LITERAL",
89
+ message: `Union '${unionTypeName}' member '${memberName}' has discriminator field '${primaryFieldName}' but its type is not a string literal.`,
90
+ severity: "error",
91
+ location: sourceLocation,
92
+ });
93
+ return null;
94
+ }
95
+
96
+ return value;
97
+ }
98
+
99
+ /**
100
+ * Validates primary (first) discriminator field for an inline object member.
101
+ * Returns the string literal value if valid, or null if an error was reported.
102
+ */
103
+ function validatePrimaryFieldForInlineMember(
104
+ inlineMember: InlineObjectMember,
105
+ primaryFieldName: string,
106
+ unionTypeName: string,
107
+ memberIndex: number,
108
+ sourceLocation: SourceLocation,
109
+ diagnostics: Diagnostic[],
110
+ ): string | null {
111
+ const tsType = findFieldInInlineMember(inlineMember, primaryFieldName);
112
+ if (tsType === null) {
113
+ diagnostics.push({
114
+ code: "DISCRIMINATOR_FIELD_NOT_FOUND",
115
+ message: `Union '${unionTypeName}' member at index ${memberIndex} does not have discriminator field '${primaryFieldName}'.`,
116
+ severity: "error",
117
+ location: sourceLocation,
118
+ });
119
+ return null;
120
+ }
121
+
122
+ const value = getStringLiteralValue(tsType);
123
+ if (value === null) {
124
+ diagnostics.push({
125
+ code: "DISCRIMINATOR_FIELD_NOT_STRING_LITERAL",
126
+ message: `Union '${unionTypeName}' member at index ${memberIndex} has discriminator field '${primaryFieldName}' but its type is not a string literal.`,
127
+ severity: "error",
128
+ location: sourceLocation,
129
+ });
130
+ return null;
131
+ }
132
+
133
+ return value;
134
+ }
135
+
136
+ /**
137
+ * Collects secondary (2nd+) discriminator field values for a named member.
138
+ * Secondary fields are optional -- returns null for absent fields.
139
+ */
140
+ function collectSecondaryValuesForNamedMember(
141
+ memberType: ExtractedTypeInfo,
142
+ secondaryFieldNames: ReadonlyArray<string>,
143
+ ): ReadonlyArray<string | null> {
144
+ return secondaryFieldNames.map((fieldName) => {
145
+ const field = findFieldInNamedMember(memberType, fieldName);
146
+ if (field === null) {
147
+ return null;
148
+ }
149
+ return getStringLiteralValue(field.tsType);
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Collects secondary (2nd+) discriminator field values for an inline object member.
155
+ * Secondary fields are optional -- returns null for absent fields.
156
+ */
157
+ function collectSecondaryValuesForInlineMember(
158
+ inlineMember: InlineObjectMember,
159
+ secondaryFieldNames: ReadonlyArray<string>,
160
+ ): ReadonlyArray<string | null> {
161
+ return secondaryFieldNames.map((fieldName) => {
162
+ const tsType = findFieldInInlineMember(inlineMember, fieldName);
163
+ if (tsType === null) {
164
+ return null;
165
+ }
166
+ return getStringLiteralValue(tsType);
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Checks that all member value tuples are unique. Reports DISCRIMINATOR_DUPLICATE_VALUE_TUPLE
172
+ * for any duplicates found.
173
+ */
174
+ /**
175
+ * Returns true if duplicates were found (diagnostics were added).
176
+ */
177
+ function validateValueTupleUniqueness(
178
+ memberTuples: ReadonlyArray<{
179
+ readonly id: MemberIdentifier;
180
+ readonly values: ReadonlyArray<string | null>;
181
+ }>,
182
+ unionTypeName: string,
183
+ sourceLocation: SourceLocation,
184
+ diagnostics: Diagnostic[],
185
+ ): boolean {
186
+ // Group members by their serialized value tuple
187
+ const tupleGroups = new Map<string, string[]>();
188
+ for (const { id, values } of memberTuples) {
189
+ const key = JSON.stringify(values);
190
+ let group = tupleGroups.get(key);
191
+ if (group === undefined) {
192
+ group = [];
193
+ tupleGroups.set(key, group);
194
+ }
195
+ group.push(id.label);
196
+ }
197
+
198
+ let hasDuplicates = false;
199
+ for (const [tupleKey, members] of tupleGroups) {
200
+ if (members.length > 1) {
201
+ hasDuplicates = true;
202
+ const tupleDisplay = tupleKey;
203
+ diagnostics.push({
204
+ code: "DISCRIMINATOR_DUPLICATE_VALUE_TUPLE",
205
+ message: `Union '${unionTypeName}' has duplicate discriminator value tuple ${tupleDisplay} for members ${members.map((m) => `'${m}'`).join(", ")}.`,
206
+ severity: "error",
207
+ location: sourceLocation,
208
+ });
209
+ }
210
+ }
211
+ return hasDuplicates;
212
+ }
213
+
214
+ /**
215
+ * Validates that the primary (first) discriminator field exists on all union members
216
+ * and has a string literal type value. Also validates that the value tuples
217
+ * (primary + secondary field values) are unique across all members.
218
+ *
219
+ * Reports diagnostics for:
220
+ * - DISCRIMINATOR_FIELD_NOT_FOUND: field does not exist on a member
221
+ * - DISCRIMINATOR_FIELD_NOT_STRING_LITERAL: field exists but is not a string literal type
222
+ * - DISCRIMINATOR_DUPLICATE_VALUE_TUPLE: value tuples are not unique across members
223
+ */
224
+ export function validateDiscriminatorFields(
225
+ params: ValidateDiscriminatorFieldsParams,
226
+ ): ValidateDiscriminatorFieldsResult {
227
+ const { discriminatorFields, typeMap, inlineDiscriminatorUnionNames } =
228
+ params;
229
+ const diagnostics: Diagnostic[] = [];
230
+ const validatedEntries: ValidatedDiscriminatorEntry[] = [];
231
+
232
+ for (const [unionTypeName, fieldNames] of discriminatorFields) {
233
+ const unionType = typeMap.get(unionTypeName);
234
+ if (unionType === undefined) {
235
+ // Skip warning for unions already handled by inline union flattening
236
+ if (!inlineDiscriminatorUnionNames.has(unionTypeName)) {
237
+ diagnostics.push({
238
+ code: "DISCRIMINATOR_UNKNOWN_UNION",
239
+ message: `Union type '${unionTypeName}' specified in discriminatorFields does not exist.`,
240
+ severity: "warning",
241
+ location: null,
242
+ });
243
+ }
244
+ continue;
245
+ }
246
+
247
+ if (unionType.metadata.kind !== "union") {
248
+ continue;
249
+ }
250
+
251
+ const primaryFieldName = fieldNames[0];
252
+ if (primaryFieldName === undefined) {
253
+ continue;
254
+ }
255
+
256
+ const sourceLocation = unionType.metadata.sourceLocation;
257
+ const secondaryFieldNames = fieldNames.slice(1);
258
+
259
+ // Track whether any primary field errors were found for this union.
260
+ // If so, skip the value tuple uniqueness check since tuples would be incomplete.
261
+ let hasPrimaryErrors = false;
262
+
263
+ // Collect value tuples for all members (used for both validation and output)
264
+ const memberTuples: {
265
+ readonly id: MemberIdentifier;
266
+ readonly values: ReadonlyArray<string | null>;
267
+ readonly memberTypeName: string | null;
268
+ readonly memberIndex: number;
269
+ readonly isInlineObject: boolean;
270
+ }[] = [];
271
+
272
+ // Validate named members
273
+ const namedMembers = unionType.unionMembers ?? [];
274
+ for (let memberIdx = 0; memberIdx < namedMembers.length; memberIdx++) {
275
+ const memberName = namedMembers[memberIdx]!;
276
+ const memberType = typeMap.get(memberName);
277
+ if (memberType === undefined) {
278
+ continue;
279
+ }
280
+
281
+ const primaryValue = validatePrimaryFieldForNamedMember(
282
+ memberType,
283
+ primaryFieldName,
284
+ unionTypeName,
285
+ memberName,
286
+ sourceLocation,
287
+ diagnostics,
288
+ );
289
+
290
+ if (primaryValue === null) {
291
+ hasPrimaryErrors = true;
292
+ continue;
293
+ }
294
+
295
+ const secondaryValues = collectSecondaryValuesForNamedMember(
296
+ memberType,
297
+ secondaryFieldNames,
298
+ );
299
+ memberTuples.push({
300
+ id: { label: memberName },
301
+ values: [primaryValue, ...secondaryValues],
302
+ memberTypeName: memberName,
303
+ memberIndex: memberIdx,
304
+ isInlineObject: false,
305
+ });
306
+ }
307
+
308
+ // Validate inline object members
309
+ const inlineObjectMembers = unionType.inlineObjectMembers ?? [];
310
+ const namedMemberCount = namedMembers.length;
311
+ for (let i = 0; i < inlineObjectMembers.length; i++) {
312
+ const inlineMember = inlineObjectMembers[i]!;
313
+ const memberIndex = namedMemberCount + i;
314
+
315
+ const primaryValue = validatePrimaryFieldForInlineMember(
316
+ inlineMember,
317
+ primaryFieldName,
318
+ unionTypeName,
319
+ memberIndex,
320
+ sourceLocation,
321
+ diagnostics,
322
+ );
323
+
324
+ if (primaryValue === null) {
325
+ hasPrimaryErrors = true;
326
+ continue;
327
+ }
328
+
329
+ const secondaryValues = collectSecondaryValuesForInlineMember(
330
+ inlineMember,
331
+ secondaryFieldNames,
332
+ );
333
+ memberTuples.push({
334
+ id: { label: `member${memberIndex}` },
335
+ values: [primaryValue, ...secondaryValues],
336
+ memberTypeName: null,
337
+ memberIndex,
338
+ isInlineObject: true,
339
+ });
340
+ }
341
+
342
+ // Only check uniqueness when all primary fields are valid
343
+ if (!hasPrimaryErrors && memberTuples.length > 0) {
344
+ const hasDuplicateErrors = validateValueTupleUniqueness(
345
+ memberTuples,
346
+ unionTypeName,
347
+ sourceLocation,
348
+ diagnostics,
349
+ );
350
+
351
+ // Only produce validated entries when validation passed (no primary errors, no duplicates)
352
+ if (!hasDuplicateErrors) {
353
+ validatedEntries.push({
354
+ unionTypeName,
355
+ fieldNames: [...fieldNames],
356
+ memberValueTuples: memberTuples.map((t) => ({
357
+ memberTypeName: t.memberTypeName,
358
+ memberIndex: t.memberIndex,
359
+ values: t.values,
360
+ isInlineObject: t.isInlineObject,
361
+ })),
362
+ });
363
+ }
364
+ }
365
+ }
366
+
367
+ return { diagnostics, validatedEntries };
368
+ }
@@ -0,0 +1,24 @@
1
+ import { toPascalCase } from "./naming-convention.js";
2
+
3
+ export interface GenerateDiscriminatorMemberNameParams {
4
+ readonly unionTypeName: string;
5
+ readonly values: ReadonlyArray<string | null>;
6
+ }
7
+
8
+ /**
9
+ * Generates a member type name for an inline union member based on discriminator field values.
10
+ * Each non-null value is converted to PascalCase and appended to the union type name.
11
+ * Null values (fields absent from the member) are skipped.
12
+ */
13
+ export function generateDiscriminatorMemberName(
14
+ params: GenerateDiscriminatorMemberNameParams,
15
+ ): string {
16
+ const { unionTypeName, values } = params;
17
+
18
+ const suffix = values
19
+ .filter((v): v is string => v !== null)
20
+ .map(toPascalCase)
21
+ .join("");
22
+
23
+ return `${unionTypeName}${suffix}`;
24
+ }
@@ -0,0 +1,136 @@
1
+ import type { ExtractedTypeInfo } from "../type-extractor/types/index.js";
2
+ import type { AutoGeneratedType } from "./auto-type-generator.js";
3
+ import { generateDiscriminatorMemberName } from "./discriminator-naming.js";
4
+ import { generateObjectTypeFromInlineObject } from "./inline-object-converter.js";
5
+
6
+ export interface MemberValueTuple {
7
+ readonly memberTypeName: string | null;
8
+ readonly memberIndex: number;
9
+ readonly values: ReadonlyArray<string | null>;
10
+ readonly isInlineObject: boolean;
11
+ }
12
+
13
+ export interface ValidatedDiscriminatorEntry {
14
+ readonly unionTypeName: string;
15
+ readonly fieldNames: ReadonlyArray<string>;
16
+ readonly memberValueTuples: ReadonlyArray<MemberValueTuple>;
17
+ }
18
+
19
+ export interface DiscriminatorValueMapping {
20
+ readonly memberGraphQLTypeName: string;
21
+ readonly values: ReadonlyArray<string | null>;
22
+ }
23
+
24
+ export interface DiscriminatorResolveTypeInfo {
25
+ readonly unionTypeName: string;
26
+ readonly fieldNames: ReadonlyArray<string>;
27
+ readonly valueMappings: ReadonlyArray<DiscriminatorValueMapping>;
28
+ }
29
+
30
+ export interface CollectDiscriminatorResolveTypesParams {
31
+ readonly validatedEntries: ReadonlyArray<ValidatedDiscriminatorEntry>;
32
+ readonly manualResolveTypeNames: ReadonlySet<string>;
33
+ readonly extractedTypes: ReadonlyArray<ExtractedTypeInfo>;
34
+ readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
35
+ }
36
+
37
+ export interface CollectDiscriminatorResolveTypesResult {
38
+ readonly discriminatorResolveTypes: ReadonlyArray<DiscriminatorResolveTypeInfo>;
39
+ readonly discriminatorResolveTypeNames: ReadonlySet<string>;
40
+ readonly generatedObjectTypes: ReadonlyArray<AutoGeneratedType>;
41
+ }
42
+
43
+ /**
44
+ * Determines the GraphQL type name for a union member based on discriminator values.
45
+ * Named members use their existing type name; inline members get a generated name.
46
+ */
47
+ function resolveGraphQLTypeName(
48
+ tuple: MemberValueTuple,
49
+ unionTypeName: string,
50
+ ): string {
51
+ if (tuple.memberTypeName !== null) {
52
+ return tuple.memberTypeName;
53
+ }
54
+
55
+ return generateDiscriminatorMemberName({
56
+ unionTypeName,
57
+ values: tuple.values,
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Collects resolveType pattern information from validated discriminator field entries.
63
+ *
64
+ * For each validated entry:
65
+ * - Skips unions that have manual defineResolveType
66
+ * - Maps discriminator field values to GraphQL type names
67
+ * - Generates object types for inline members
68
+ * - Adds union names to discriminatorResolveTypeNames to suppress MISSING_ABSTRACT_TYPE_RESOLVER
69
+ */
70
+ export function collectDiscriminatorResolveTypes(
71
+ params: CollectDiscriminatorResolveTypesParams,
72
+ ): CollectDiscriminatorResolveTypesResult {
73
+ const { validatedEntries, manualResolveTypeNames, typeMap } = params;
74
+
75
+ const discriminatorResolveTypes: DiscriminatorResolveTypeInfo[] = [];
76
+ const discriminatorResolveTypeNames = new Set<string>();
77
+ const generatedObjectTypes: AutoGeneratedType[] = [];
78
+
79
+ for (const entry of validatedEntries) {
80
+ // Skip unions that have a manual defineResolveType
81
+ if (manualResolveTypeNames.has(entry.unionTypeName)) {
82
+ continue;
83
+ }
84
+
85
+ const unionType = typeMap.get(entry.unionTypeName);
86
+ if (unionType === undefined) {
87
+ continue;
88
+ }
89
+
90
+ const valueMappings: DiscriminatorValueMapping[] = [];
91
+ const inlineObjectMembers = unionType.inlineObjectMembers ?? [];
92
+ const namedMemberCount = unionType.unionMembers?.length ?? 0;
93
+
94
+ for (const tuple of entry.memberValueTuples) {
95
+ const graphQLTypeName = resolveGraphQLTypeName(
96
+ tuple,
97
+ entry.unionTypeName,
98
+ );
99
+
100
+ valueMappings.push({
101
+ memberGraphQLTypeName: graphQLTypeName,
102
+ values: tuple.values,
103
+ });
104
+
105
+ // Generate object types for inline members
106
+ if (tuple.isInlineObject) {
107
+ const inlineIndex = tuple.memberIndex - namedMemberCount;
108
+ const inlineMember = inlineObjectMembers[inlineIndex];
109
+ if (inlineMember !== undefined) {
110
+ const objectType = generateObjectTypeFromInlineObject({
111
+ inlineObjectMember: inlineMember,
112
+ typeName: graphQLTypeName,
113
+ abstractTypeName: entry.unionTypeName,
114
+ sourceFile: unionType.metadata.sourceFile,
115
+ skipPropertyNames: new Set(),
116
+ });
117
+ generatedObjectTypes.push(objectType);
118
+ }
119
+ }
120
+ }
121
+
122
+ discriminatorResolveTypes.push({
123
+ unionTypeName: entry.unionTypeName,
124
+ fieldNames: entry.fieldNames,
125
+ valueMappings,
126
+ });
127
+
128
+ discriminatorResolveTypeNames.add(entry.unionTypeName);
129
+ }
130
+
131
+ return {
132
+ discriminatorResolveTypes,
133
+ discriminatorResolveTypeNames,
134
+ generatedObjectTypes,
135
+ };
136
+ }
@@ -7,6 +7,16 @@ export {
7
7
  type GeneratedFromInfo,
8
8
  generateAutoTypes,
9
9
  } from "./auto-type-generator.js";
10
+ export {
11
+ type ValidateDiscriminatorFieldsParams,
12
+ type ValidateDiscriminatorFieldsResult,
13
+ validateDiscriminatorFields,
14
+ } from "./discriminator-field-validator.js";
15
+ export {
16
+ type CollectDiscriminatorResolveTypesParams,
17
+ type CollectDiscriminatorResolveTypesResult,
18
+ collectDiscriminatorResolveTypes,
19
+ } from "./discriminator-resolve-type-generator.js";
10
20
  export type {
11
21
  InlineUnionMemberInfo,
12
22
  InlineUnionWithContext,
@@ -16,6 +26,13 @@ export type {
16
26
  ValidateUnionMembersParams,
17
27
  ValidateUnionResult,
18
28
  } from "./inline-union-validator.js";
29
+ export {
30
+ type FlattenInlineUnionMembersParams,
31
+ type FlattenInlineUnionMembersResult,
32
+ type FlattenIntersectionMembersParams,
33
+ flattenIntersectionMembers,
34
+ type InlineDiscriminatorResolveType,
35
+ } from "./intersection-flattener.js";
19
36
  export {
20
37
  type NameCollisionValidatorInput,
21
38
  type NameCollisionValidatorResult,
@@ -9,9 +9,13 @@ import type {
9
9
  InlineObjectPropertyDef,
10
10
  SourceLocation,
11
11
  } from "../type-extractor/types/index.js";
12
- import { traverseInlineObjectProperties } from "./inline-object-traverser.js";
12
+ import {
13
+ getInlineObjectPropertiesFromType,
14
+ traverseInlineObjectProperties,
15
+ } from "./inline-object-traverser.js";
13
16
  import {
14
17
  type AutoTypeNameContext,
18
+ appendFieldPath,
15
19
  buildFieldContext,
16
20
  isInputTypeName,
17
21
  } from "./naming-convention.js";
@@ -47,6 +51,9 @@ export function collectInlineEnumsFromTypes(
47
51
 
48
52
  for (const typeInfo of extractedTypes) {
49
53
  const isInput = isInputTypeName(typeInfo.metadata.name);
54
+ const siblingFieldNames = new Set(
55
+ typeInfo.fields.map((field) => field.name),
56
+ );
50
57
 
51
58
  for (const field of typeInfo.fields) {
52
59
  collectInlineEnumsFromField(
@@ -55,6 +62,7 @@ export function collectInlineEnumsFromTypes(
55
62
  [],
56
63
  isInput,
57
64
  typeInfo.metadata.sourceFile,
65
+ siblingFieldNames,
58
66
  results,
59
67
  );
60
68
  }
@@ -69,10 +77,16 @@ function collectInlineEnumsFromField(
69
77
  parentPath: ReadonlyArray<string>,
70
78
  isInput: boolean,
71
79
  sourceFile: string,
80
+ siblingFieldNames: ReadonlySet<string>,
72
81
  results: InlineEnumWithContext[],
73
82
  ): void {
74
83
  const tsType = field.tsType;
75
- const fieldPath = [...parentPath, field.name];
84
+ const fieldPath = appendFieldPath({
85
+ parentPath,
86
+ fieldName: field.name,
87
+ singularize: tsType.kind === "array",
88
+ siblingFieldNames,
89
+ });
76
90
 
77
91
  if (tsType.kind === "inlineEnum" && tsType.inlineEnumMembers) {
78
92
  results.push({
@@ -108,9 +122,10 @@ function collectInlineEnumsFromField(
108
122
  });
109
123
  }
110
124
 
111
- if (tsType.kind === "inlineObject" && tsType.inlineObjectProperties) {
125
+ const inlineObjectProperties = getInlineObjectPropertiesFromType(tsType);
126
+ if (inlineObjectProperties) {
112
127
  traverseInlineObjectProperties(
113
- { properties: tsType.inlineObjectProperties, parentPath: fieldPath },
128
+ { properties: inlineObjectProperties, parentPath: fieldPath },
114
129
  (prop, propPath) => {
115
130
  const propTsType = prop.tsType;
116
131
  if (propTsType.kind === "inlineEnum" && propTsType.inlineEnumMembers) {