@gqlkit-ts/cli 0.5.1 → 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 (212) 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 +379 -56
  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 +23 -2
  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/resolver-field-iterator.d.ts +1 -1
  48. package/dist/auto-type-generator/resolver-field-iterator.d.ts.map +1 -1
  49. package/dist/auto-type-generator/resolver-field-iterator.js +3 -0
  50. package/dist/auto-type-generator/resolver-field-iterator.js.map +1 -1
  51. package/dist/auto-type-generator/typename-extractor.d.ts +2 -0
  52. package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -1
  53. package/dist/auto-type-generator/typename-extractor.js +11 -3
  54. package/dist/auto-type-generator/typename-extractor.js.map +1 -1
  55. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +2 -0
  56. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -1
  57. package/dist/auto-type-generator/typename-resolve-type-generator.js +12 -84
  58. package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -1
  59. package/dist/auto-type-generator/typename-types.d.ts +4 -0
  60. package/dist/auto-type-generator/typename-types.d.ts.map +1 -1
  61. package/dist/auto-type-generator/typename-types.js +6 -0
  62. package/dist/auto-type-generator/typename-types.js.map +1 -1
  63. package/dist/auto-type-generator/typename-validator.d.ts.map +1 -1
  64. package/dist/auto-type-generator/typename-validator.js +4 -3
  65. package/dist/auto-type-generator/typename-validator.js.map +1 -1
  66. package/dist/commands/docs.d.ts +1 -0
  67. package/dist/commands/docs.d.ts.map +1 -1
  68. package/dist/commands/gen.d.ts +1 -0
  69. package/dist/commands/gen.d.ts.map +1 -1
  70. package/dist/commands/gen.js +2 -1
  71. package/dist/commands/gen.js.map +1 -1
  72. package/dist/commands/main.d.ts +1 -0
  73. package/dist/commands/main.d.ts.map +1 -1
  74. package/dist/config/types.d.ts +7 -0
  75. package/dist/config/types.d.ts.map +1 -1
  76. package/dist/config-loader/index.d.ts +1 -1
  77. package/dist/config-loader/index.d.ts.map +1 -1
  78. package/dist/config-loader/index.js.map +1 -1
  79. package/dist/config-loader/loader.d.ts +6 -0
  80. package/dist/config-loader/loader.d.ts.map +1 -1
  81. package/dist/config-loader/loader.js +1 -0
  82. package/dist/config-loader/loader.js.map +1 -1
  83. package/dist/config-loader/validator.d.ts.map +1 -1
  84. package/dist/config-loader/validator.js +84 -1
  85. package/dist/config-loader/validator.js.map +1 -1
  86. package/dist/gen-orchestrator/orchestrator.d.ts +2 -1
  87. package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
  88. package/dist/gen-orchestrator/orchestrator.js +43 -3
  89. package/dist/gen-orchestrator/orchestrator.js.map +1 -1
  90. package/dist/resolver-extractor/extract-resolvers.d.ts +4 -0
  91. package/dist/resolver-extractor/extract-resolvers.d.ts.map +1 -1
  92. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts +2 -1
  93. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
  94. package/dist/resolver-extractor/extractor/define-api-extractor.js +35 -6
  95. package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
  96. package/dist/resolver-extractor/index.d.ts +1 -1
  97. package/dist/resolver-extractor/index.d.ts.map +1 -1
  98. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +2 -0
  99. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts.map +1 -1
  100. package/dist/resolver-extractor/validator/abstract-resolver-validator.js +16 -3
  101. package/dist/resolver-extractor/validator/abstract-resolver-validator.js.map +1 -1
  102. package/dist/schema-generator/emitter/code-emitter.d.ts.map +1 -1
  103. package/dist/schema-generator/emitter/code-emitter.js +24 -4
  104. package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
  105. package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.d.ts +18 -0
  106. package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.d.ts.map +1 -0
  107. package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.js +89 -0
  108. package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.js.map +1 -0
  109. package/dist/schema-generator/generate-schema.d.ts +2 -0
  110. package/dist/schema-generator/generate-schema.d.ts.map +1 -1
  111. package/dist/schema-generator/generate-schema.js +69 -10
  112. package/dist/schema-generator/generate-schema.js.map +1 -1
  113. package/dist/schema-generator/integrator/result-integrator.d.ts +5 -0
  114. package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
  115. package/dist/schema-generator/integrator/result-integrator.js +44 -3
  116. package/dist/schema-generator/integrator/result-integrator.js.map +1 -1
  117. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts +2 -0
  118. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts.map +1 -1
  119. package/dist/schema-generator/resolver-collector/resolver-collector.js +4 -0
  120. package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
  121. package/dist/shared/constants.d.ts.map +1 -1
  122. package/dist/shared/constants.js +14 -1
  123. package/dist/shared/constants.js.map +1 -1
  124. package/dist/shared/enum-prefix-detector.d.ts.map +1 -1
  125. package/dist/shared/enum-prefix-detector.js +78 -8
  126. package/dist/shared/enum-prefix-detector.js.map +1 -1
  127. package/dist/shared/inline-object-utils.js +1 -1
  128. package/dist/shared/inline-object-utils.js.map +1 -1
  129. package/dist/shared/type-converter.d.ts.map +1 -1
  130. package/dist/shared/type-converter.js +55 -0
  131. package/dist/shared/type-converter.js.map +1 -1
  132. package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
  133. package/dist/type-extractor/converter/graphql-converter.js +11 -1
  134. package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
  135. package/dist/type-extractor/extractor/field-type-resolver.d.ts +18 -0
  136. package/dist/type-extractor/extractor/field-type-resolver.d.ts.map +1 -1
  137. package/dist/type-extractor/extractor/field-type-resolver.js +198 -15
  138. package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -1
  139. package/dist/type-extractor/extractor/type-extractor.d.ts +1 -0
  140. package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
  141. package/dist/type-extractor/extractor/type-extractor.js +100 -9
  142. package/dist/type-extractor/extractor/type-extractor.js.map +1 -1
  143. package/dist/type-extractor/types/diagnostics.d.ts +1 -1
  144. package/dist/type-extractor/types/diagnostics.d.ts.map +1 -1
  145. package/dist/type-extractor/types/index.d.ts +1 -1
  146. package/dist/type-extractor/types/index.d.ts.map +1 -1
  147. package/dist/type-extractor/types/index.js +1 -1
  148. package/dist/type-extractor/types/index.js.map +1 -1
  149. package/dist/type-extractor/types/ts-type-reference-factory.d.ts +7 -1
  150. package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -1
  151. package/dist/type-extractor/types/ts-type-reference-factory.js +18 -3
  152. package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -1
  153. package/dist/type-extractor/types/typescript.d.ts +3 -1
  154. package/dist/type-extractor/types/typescript.d.ts.map +1 -1
  155. package/dist/type-extractor/validator/type-validator.d.ts.map +1 -1
  156. package/dist/type-extractor/validator/type-validator.js +6 -1
  157. package/dist/type-extractor/validator/type-validator.js.map +1 -1
  158. package/docs/configuration.md +19 -0
  159. package/docs/getting-started.md +2 -1
  160. package/docs/index.md +2 -0
  161. package/docs/integration/ai-sdk.md +189 -0
  162. package/docs/schema/conventions.md +7 -0
  163. package/docs/schema/fields.md +15 -0
  164. package/docs/schema/queries-mutations.md +21 -2
  165. package/docs/schema/subscriptions.md +173 -0
  166. package/docs/schema/unions.md +117 -0
  167. package/package.json +4 -4
  168. package/src/auto-type-generator/auto-type-generator.ts +588 -62
  169. package/src/auto-type-generator/discriminator-field-validator.ts +368 -0
  170. package/src/auto-type-generator/discriminator-naming.ts +24 -0
  171. package/src/auto-type-generator/discriminator-resolve-type-generator.ts +136 -0
  172. package/src/auto-type-generator/index.ts +17 -0
  173. package/src/auto-type-generator/inline-enum-collector.ts +19 -4
  174. package/src/auto-type-generator/inline-object-converter.ts +100 -0
  175. package/src/auto-type-generator/inline-object-traverser.ts +33 -7
  176. package/src/auto-type-generator/inline-union-collector.ts +26 -4
  177. package/src/auto-type-generator/inline-union-types.ts +2 -0
  178. package/src/auto-type-generator/inline-union-validator.ts +3 -3
  179. package/src/auto-type-generator/intersection-flattener.ts +554 -0
  180. package/src/auto-type-generator/naming-convention.ts +207 -3
  181. package/src/auto-type-generator/resolver-field-iterator.ts +5 -1
  182. package/src/auto-type-generator/typename-extractor.ts +17 -3
  183. package/src/auto-type-generator/typename-resolve-type-generator.ts +19 -108
  184. package/src/auto-type-generator/typename-types.ts +7 -0
  185. package/src/auto-type-generator/typename-validator.ts +4 -3
  186. package/src/commands/gen.ts +9 -2
  187. package/src/config/types.ts +10 -0
  188. package/src/config-loader/index.ts +1 -0
  189. package/src/config-loader/loader.ts +11 -0
  190. package/src/config-loader/validator.ts +100 -1
  191. package/src/gen-orchestrator/orchestrator.ts +50 -3
  192. package/src/resolver-extractor/extract-resolvers.ts +5 -0
  193. package/src/resolver-extractor/extractor/define-api-extractor.ts +47 -7
  194. package/src/resolver-extractor/index.ts +1 -0
  195. package/src/resolver-extractor/validator/abstract-resolver-validator.ts +20 -6
  196. package/src/schema-generator/emitter/code-emitter.ts +43 -5
  197. package/src/schema-generator/emitter/discriminator-resolve-type-emitter.ts +125 -0
  198. package/src/schema-generator/generate-schema.ts +100 -13
  199. package/src/schema-generator/integrator/result-integrator.ts +55 -2
  200. package/src/schema-generator/resolver-collector/resolver-collector.ts +7 -0
  201. package/src/shared/constants.ts +15 -1
  202. package/src/shared/enum-prefix-detector.ts +96 -8
  203. package/src/shared/inline-object-utils.ts +1 -1
  204. package/src/shared/type-converter.ts +63 -0
  205. package/src/type-extractor/converter/graphql-converter.ts +17 -1
  206. package/src/type-extractor/extractor/field-type-resolver.ts +241 -16
  207. package/src/type-extractor/extractor/type-extractor.ts +119 -5
  208. package/src/type-extractor/types/diagnostics.ts +10 -1
  209. package/src/type-extractor/types/index.ts +2 -1
  210. package/src/type-extractor/types/ts-type-reference-factory.ts +24 -3
  211. package/src/type-extractor/types/typescript.ts +6 -2
  212. package/src/type-extractor/validator/type-validator.ts +6 -1
@@ -0,0 +1,125 @@
1
+ import type {
2
+ DiscriminatorResolveTypeInfo,
3
+ DiscriminatorValueMapping,
4
+ } from "../../auto-type-generator/discriminator-resolve-type-generator.js";
5
+
6
+ /**
7
+ * Groups value mappings by their value at a given field index.
8
+ * Null values at the field index mean the member doesn't have that field,
9
+ * so the member should be resolved directly at the current switch level.
10
+ */
11
+ function groupMappingsByFieldValue(
12
+ mappings: ReadonlyArray<DiscriminatorValueMapping>,
13
+ fieldIndex: number,
14
+ ): Map<string | null, DiscriminatorValueMapping[]> {
15
+ const groups = new Map<string | null, DiscriminatorValueMapping[]>();
16
+ for (const mapping of mappings) {
17
+ const value = mapping.values[fieldIndex] ?? null;
18
+ const group = groups.get(value) ?? [];
19
+ group.push(mapping);
20
+ groups.set(value, group);
21
+ }
22
+ return groups;
23
+ }
24
+
25
+ /**
26
+ * Builds a switch statement body for a given field level, recursing for nested fields.
27
+ * Returns an array of code lines (without leading indentation for the switch itself).
28
+ */
29
+ function buildSwitchBody(
30
+ mappings: ReadonlyArray<DiscriminatorValueMapping>,
31
+ fieldNames: ReadonlyArray<string>,
32
+ fieldIndex: number,
33
+ indent: string,
34
+ ): string[] {
35
+ const fieldName = fieldNames[fieldIndex]!;
36
+ const groups = groupMappingsByFieldValue(mappings, fieldIndex);
37
+ const lines: string[] = [];
38
+
39
+ lines.push(`${indent}switch (obj.${fieldName}) {`);
40
+
41
+ for (const [value, groupMappings] of groups) {
42
+ // Null value means the member doesn't have this field — resolved at the parent level
43
+ if (value === null) {
44
+ for (const mapping of groupMappings) {
45
+ lines.push(
46
+ `${indent} case "${mapping.values[fieldIndex - 1]!}": return "${mapping.memberGraphQLTypeName}";`,
47
+ );
48
+ }
49
+ continue;
50
+ }
51
+
52
+ const nextFieldIndex = fieldIndex + 1;
53
+ const hasMoreFields = nextFieldIndex < fieldNames.length;
54
+
55
+ // Check if all mappings in this group can be resolved directly
56
+ // (either no more fields, or only one mapping, or all remaining values are null)
57
+ const canResolveDirectly =
58
+ groupMappings.length === 1 &&
59
+ (!hasMoreFields || groupMappings[0]!.values[nextFieldIndex] === null);
60
+
61
+ if (!hasMoreFields || canResolveDirectly) {
62
+ // Single mapping or no more fields: emit direct return
63
+ if (groupMappings.length === 1) {
64
+ lines.push(
65
+ `${indent} case "${value}": return "${groupMappings[0]!.memberGraphQLTypeName}";`,
66
+ );
67
+ } else {
68
+ // Multiple mappings at the leaf level — shouldn't happen with unique tuples
69
+ for (const mapping of groupMappings) {
70
+ lines.push(
71
+ `${indent} case "${value}": return "${mapping.memberGraphQLTypeName}";`,
72
+ );
73
+ }
74
+ }
75
+ } else {
76
+ // Need nested switch for remaining fields
77
+ lines.push(`${indent} case "${value}":`);
78
+ const nestedLines = buildSwitchBody(
79
+ groupMappings,
80
+ fieldNames,
81
+ nextFieldIndex,
82
+ `${indent} `,
83
+ );
84
+ lines.push(...nestedLines);
85
+ }
86
+ }
87
+
88
+ lines.push(`${indent} default: return undefined;`);
89
+ lines.push(`${indent}}`);
90
+
91
+ return lines;
92
+ }
93
+
94
+ function buildObjTypeAnnotation(fieldNames: ReadonlyArray<string>): string {
95
+ const fields = fieldNames.map((name) => `${name}: string`);
96
+ return `{ ${fields.join("; ")} }`;
97
+ }
98
+
99
+ /**
100
+ * Builds a resolver map entry for a discriminator-based __resolveType.
101
+ *
102
+ * For single discriminator field:
103
+ * TypeName: {
104
+ * __resolveType: (obj: { field: string }) => {
105
+ * switch (obj.field) {
106
+ * case "val": return "MemberType";
107
+ * default: return undefined;
108
+ * }
109
+ * },
110
+ * },
111
+ *
112
+ * For multiple discriminator fields, generates nested switch statements.
113
+ */
114
+ export function buildDiscriminatorResolveTypeEntry(
115
+ info: DiscriminatorResolveTypeInfo,
116
+ ): string {
117
+ const { unionTypeName, fieldNames, valueMappings } = info;
118
+ const objType = buildObjTypeAnnotation(fieldNames);
119
+ const baseIndent = " ";
120
+
121
+ const switchLines = buildSwitchBody(valueMappings, fieldNames, 0, baseIndent);
122
+ const switchBody = switchLines.join("\n");
123
+
124
+ return ` ${unionTypeName}: {\n __resolveType: (obj: ${objType}) => {\n${switchBody}\n },\n },`;
125
+ }
@@ -1,12 +1,16 @@
1
1
  import {
2
+ collectDiscriminatorResolveTypes,
2
3
  collectTypenameExtractions,
3
4
  collectTypenameResolveTypes,
5
+ flattenIntersectionMembers,
4
6
  generateAutoTypes,
7
+ validateDiscriminatorFields,
5
8
  validateNameCollisions,
6
9
  validateSchemaTypenames,
7
10
  validateTypenames,
8
11
  } from "../auto-type-generator/index.js";
9
12
  import type { ImportExtension } from "../config/types.js";
13
+ import type { ResolvedDiscriminatorFieldsMap } from "../config-loader/index.js";
10
14
  import type { ExtractResolversResult } from "../resolver-extractor/index.js";
11
15
  import type { DirectiveDefinitionInfo } from "../shared/directive-definition-extractor.js";
12
16
  import type { CollectedScalarType } from "../type-extractor/collector/scalar-collector.js";
@@ -35,6 +39,7 @@ export interface GenerateSchemaInput {
35
39
  readonly sourceRoot: string | null;
36
40
  readonly knownTypeNames: ReadonlySet<string> | null;
37
41
  readonly importExtension: ImportExtension;
42
+ readonly discriminatorFields: ResolvedDiscriminatorFieldsMap;
38
43
  }
39
44
 
40
45
  export interface GenerateSchemaResult {
@@ -67,6 +72,7 @@ export function generateSchema(
67
72
  extractedTypes,
68
73
  resolversResult,
69
74
  knownTypeNames: knownTypeNames ?? new Set(),
75
+ discriminatorFields: input.discriminatorFields,
70
76
  });
71
77
 
72
78
  const autoTypeErrors = autoTypeResult.diagnostics.filter(
@@ -105,11 +111,52 @@ export function generateSchema(
105
111
  };
106
112
  }
107
113
 
114
+ // Flatten intersection-expanded inline object members for discriminator unions.
115
+ // TypeScript distributes intersections over unions, creating duplicate members
116
+ // that must be merged before discriminator field validation.
117
+ const flattenedExtractedTypes = flattenIntersectionMembers({
118
+ extractedTypes: autoTypeResult.updatedExtractedTypes,
119
+ discriminatorFields: input.discriminatorFields,
120
+ });
121
+
108
122
  // Re-convert to GraphQL types using updated extracted types
109
123
  // This resolves __INLINE_OBJECT__ references to auto-generated type names
110
- const updatedConversionResult = convertToGraphQL(
111
- autoTypeResult.updatedExtractedTypes,
124
+ const updatedConversionResult = convertToGraphQL(flattenedExtractedTypes);
125
+ const typeMap = new Map(
126
+ flattenedExtractedTypes.map((t) => [t.metadata.name, t]),
112
127
  );
128
+
129
+ // Validate discriminator fields against extracted types
130
+ const inlineDiscriminatorUnionNames = new Set(
131
+ autoTypeResult.inlineDiscriminatorResolveTypes.map(
132
+ (rt) => rt.unionTypeName,
133
+ ),
134
+ );
135
+ const discriminatorValidationResult = validateDiscriminatorFields({
136
+ discriminatorFields: input.discriminatorFields,
137
+ extractedTypes: flattenedExtractedTypes,
138
+ typeMap,
139
+ inlineDiscriminatorUnionNames,
140
+ });
141
+ const discriminatorErrors = discriminatorValidationResult.diagnostics.filter(
142
+ (d) => d.severity === "error",
143
+ );
144
+ if (discriminatorErrors.length > 0) {
145
+ return {
146
+ typeDefsCode: "",
147
+ sdlContent: "",
148
+ resolversCode: "",
149
+ diagnostics: discriminatorValidationResult.diagnostics,
150
+ hasErrors: true,
151
+ prunedTypes: null,
152
+ };
153
+ }
154
+
155
+ const discriminatorWarnings =
156
+ discriminatorValidationResult.diagnostics.filter(
157
+ (d) => d.severity === "warning",
158
+ );
159
+
113
160
  const updatedTypesResult: ExtractTypesResult = {
114
161
  types: updatedConversionResult.types,
115
162
  diagnostics: {
@@ -126,29 +173,48 @@ export function generateSchema(
126
173
  (d) => d.severity === "warning",
127
174
  ),
128
175
  ...autoTypeResult.diagnostics.filter((d) => d.severity === "warning"),
176
+ ...discriminatorWarnings,
129
177
  ],
130
178
  },
131
179
  };
132
180
 
133
- const typeMap = new Map(
134
- autoTypeResult.updatedExtractedTypes.map((t) => [t.metadata.name, t]),
135
- );
136
-
137
181
  const manualResolveTypeNames = new Set(
138
182
  autoTypeResult.updatedResolversResult.abstractTypeResolvers
139
183
  .filter((r) => r.kind === "resolveType")
140
184
  .map((r) => r.targetTypeName),
141
185
  );
142
186
 
187
+ // Collect discriminator resolve types from validated entries
188
+ const discriminatorResolveTypesResult = collectDiscriminatorResolveTypes({
189
+ validatedEntries: discriminatorValidationResult.validatedEntries,
190
+ manualResolveTypeNames,
191
+ extractedTypes: flattenedExtractedTypes,
192
+ typeMap,
193
+ });
194
+
195
+ // Merge inline discriminator resolveTypes from auto-type generation
196
+ // (for inline unions flattened by discriminator fields, e.g. UIMessagePart<...>[])
197
+ const mergedDiscriminatorResolveTypes = [
198
+ ...discriminatorResolveTypesResult.discriminatorResolveTypes,
199
+ ...autoTypeResult.inlineDiscriminatorResolveTypes,
200
+ ];
201
+ // discriminatorResolveTypeNames are derived by integrate() from the merged list
202
+
203
+ const discriminatorFieldUnionNames = new Set(
204
+ input.discriminatorFields.keys(),
205
+ );
206
+
143
207
  const typenameResolveTypesResult = collectTypenameResolveTypes({
144
- extractedTypes: autoTypeResult.updatedExtractedTypes,
208
+ extractedTypes: flattenedExtractedTypes,
145
209
  typeMap,
146
210
  manualResolveTypeNames,
211
+ discriminatorFieldUnionNames,
147
212
  });
148
213
 
149
214
  const typenameExtractions = collectTypenameExtractions({
150
- extractedTypes: autoTypeResult.updatedExtractedTypes,
215
+ extractedTypes: flattenedExtractedTypes,
151
216
  typeMap,
217
+ discriminatorFieldUnionNames,
152
218
  });
153
219
 
154
220
  const typenameValidationDiagnostics: Diagnostic[] = [];
@@ -167,7 +233,7 @@ export function generateSchema(
167
233
  }
168
234
 
169
235
  const schemaTypenameValidationResult = validateSchemaTypenames({
170
- objectTypes: autoTypeResult.updatedExtractedTypes,
236
+ objectTypes: flattenedExtractedTypes,
171
237
  typeMap,
172
238
  });
173
239
  typenameValidationDiagnostics.push(
@@ -188,20 +254,38 @@ export function generateSchema(
188
254
  const generatedInlineObjectTypes =
189
255
  typenameResolveTypesResult.generatedInlineObjectTypes;
190
256
 
257
+ // Collect all generated object types that need to be added as union members
258
+ // (both typename-based and discriminator-based inline objects)
259
+ const discriminatorGeneratedObjectTypes =
260
+ discriminatorResolveTypesResult.generatedObjectTypes;
261
+
191
262
  const updatedTypesForIntegration: ExtractTypesResult =
192
- generatedInlineObjectTypes.length > 0
263
+ generatedInlineObjectTypes.length > 0 ||
264
+ discriminatorGeneratedObjectTypes.length > 0
193
265
  ? {
194
266
  types: updatedTypesResult.types.map((type) => {
195
267
  if (type.kind !== "Union") {
196
268
  return type;
197
269
  }
198
- const inlineTypes = generatedInlineObjectTypes.filter(
270
+ const typenameInlineTypes = generatedInlineObjectTypes.filter(
199
271
  (g) => g.abstractTypeName === type.name,
200
272
  );
201
- if (inlineTypes.length === 0) {
273
+ const discriminatorInlineTypes =
274
+ discriminatorGeneratedObjectTypes.filter(
275
+ (g) =>
276
+ g.generatedFrom !== null &&
277
+ g.generatedFrom.parentTypeName === type.name,
278
+ );
279
+ if (
280
+ typenameInlineTypes.length === 0 &&
281
+ discriminatorInlineTypes.length === 0
282
+ ) {
202
283
  return type;
203
284
  }
204
- const newMembers = inlineTypes.map((g) => g.typeName);
285
+ const newMembers = [
286
+ ...typenameInlineTypes.map((g) => g.typeName),
287
+ ...discriminatorInlineTypes.map((g) => g.name),
288
+ ];
205
289
  return {
206
290
  ...type,
207
291
  unionMembers: [
@@ -217,6 +301,7 @@ export function generateSchema(
217
301
  const allAutoGeneratedTypes = [
218
302
  ...autoTypeResult.autoGeneratedTypes,
219
303
  ...typenameResolveTypesResult.generatedObjectTypes,
304
+ ...discriminatorGeneratedObjectTypes,
220
305
  ];
221
306
 
222
307
  const integratedResult = integrate({
@@ -227,6 +312,8 @@ export function generateSchema(
227
312
  directiveDefinitions,
228
313
  autoGeneratedTypes: allAutoGeneratedTypes,
229
314
  typenameAutoResolveTypes: typenameResolveTypesResult.autoResolveTypes,
315
+ discriminatorResolveTypes: mergedDiscriminatorResolveTypes,
316
+ tsAliasToGraphQLNameMap: autoTypeResult.tsAliasToGraphQLNameMap,
230
317
  });
231
318
 
232
319
  let documentNode = buildDocumentNode(integratedResult, { sourceRoot });
@@ -1,3 +1,4 @@
1
+ import type { DiscriminatorResolveTypeInfo } from "../../auto-type-generator/discriminator-resolve-type-generator.js";
1
2
  import type {
2
3
  AutoGeneratedType,
3
4
  ResolveTypeFieldPattern,
@@ -141,10 +142,12 @@ export interface IntegratedResult {
141
142
  readonly abstractTypeResolvers: ReadonlyArray<AbstractResolverInfo>;
142
143
  readonly autoGeneratedUnions: ReadonlyArray<AutoGeneratedUnionInfo>;
143
144
  readonly typenameAutoResolveTypes: ReadonlyArray<TypenameAutoResolveTypeInfo>;
145
+ readonly discriminatorResolveTypes: ReadonlyArray<DiscriminatorResolveTypeInfo>;
144
146
  readonly numericEnums: ReadonlyArray<NumericEnumInfo>;
145
147
  readonly stringEnumMappings: ReadonlyArray<StringEnumMappingInfo>;
146
148
  readonly hasQuery: boolean;
147
149
  readonly hasMutation: boolean;
150
+ readonly hasSubscription: boolean;
148
151
  readonly hasErrors: boolean;
149
152
  readonly diagnostics: ReadonlyArray<Diagnostic>;
150
153
  }
@@ -275,6 +278,8 @@ export interface IntegrateParams {
275
278
  readonly directiveDefinitions: ReadonlyArray<DirectiveDefinitionInfo> | null;
276
279
  readonly autoGeneratedTypes: ReadonlyArray<AutoGeneratedType> | null;
277
280
  readonly typenameAutoResolveTypes: ReadonlyArray<TypenameAutoResolveTypeInfo> | null;
281
+ readonly discriminatorResolveTypes: ReadonlyArray<DiscriminatorResolveTypeInfo> | null;
282
+ readonly tsAliasToGraphQLNameMap: ReadonlyMap<string, string> | null;
278
283
  }
279
284
 
280
285
  export function integrate(params: IntegrateParams): IntegratedResult {
@@ -286,6 +291,8 @@ export function integrate(params: IntegrateParams): IntegratedResult {
286
291
  directiveDefinitions,
287
292
  autoGeneratedTypes,
288
293
  typenameAutoResolveTypes,
294
+ discriminatorResolveTypes,
295
+ tsAliasToGraphQLNameMap,
289
296
  } = params;
290
297
  const directiveTypeAliasNames = new Set(
291
298
  directiveDefinitions?.map((d) => d.typeAliasName) ?? [],
@@ -469,8 +476,10 @@ export function integrate(params: IntegrateParams): IntegratedResult {
469
476
 
470
477
  const hasQuery = resolversResult.queryFields.fields.length > 0;
471
478
  const hasMutation = resolversResult.mutationFields.fields.length > 0;
479
+ const hasSubscription = resolversResult.subscriptionFields.fields.length > 0;
472
480
 
473
- if (hasQuery) {
481
+ // GraphQL spec requires Query root type even when only Subscription/Mutation are defined
482
+ if (hasQuery || hasMutation || hasSubscription) {
474
483
  baseTypes.push({
475
484
  name: "Query",
476
485
  kind: "Object",
@@ -502,21 +511,54 @@ export function integrate(params: IntegrateParams): IntegratedResult {
502
511
  directives: null,
503
512
  });
504
513
  }
514
+ if (hasSubscription) {
515
+ baseTypes.push({
516
+ name: "Subscription",
517
+ kind: "Object",
518
+ fields: [],
519
+ unionMembers: null,
520
+ enumValues: null,
521
+ isNumericEnum: false,
522
+ needsStringEnumMapping: false,
523
+ implementedInterfaces: null,
524
+ description: null,
525
+ deprecated: null,
526
+ sourceFile: null,
527
+ directives: null,
528
+ });
529
+ }
505
530
 
506
531
  const typenameAutoResolveTypeNames = new Set([
507
532
  ...(typenameAutoResolveTypes?.map((t) => t.abstractTypeName) ?? []),
508
533
  ...autoGeneratedUnions
509
534
  .filter((u) => u.hasAutoResolveType)
510
535
  .map((u) => u.name),
536
+ ...(discriminatorResolveTypes?.map((t) => t.unionTypeName) ?? []),
511
537
  ]);
512
538
 
539
+ const aliasMap = tsAliasToGraphQLNameMap ?? new Map<string, string>();
540
+
513
541
  const abstractResolverValidation = validateAbstractResolvers({
514
542
  abstractResolvers: resolversResult.abstractTypeResolvers,
515
543
  baseTypes,
516
544
  typenameAutoResolveTypeNames,
545
+ tsAliasToGraphQLNameMap: aliasMap,
517
546
  });
518
547
  diagnostics.push(...abstractResolverValidation.diagnostics);
519
548
 
549
+ // Remap abstract resolver target type names from TS aliases to GraphQL names
550
+ // Only remap resolveType resolvers — isTypeOf targets object types, not unions
551
+ const remappedAbstractResolvers =
552
+ aliasMap.size > 0
553
+ ? resolversResult.abstractTypeResolvers.map((resolver) => {
554
+ if (resolver.kind !== "resolveType") return resolver;
555
+ const mappedName = aliasMap.get(resolver.targetTypeName);
556
+ return mappedName !== undefined
557
+ ? { ...resolver, targetTypeName: mappedName }
558
+ : resolver;
559
+ })
560
+ : resolversResult.abstractTypeResolvers;
561
+
520
562
  const knownTypeNames = new Set([
521
563
  ...baseTypes.map((t) => t.name),
522
564
  ...inputTypes.map((t) => t.name),
@@ -540,6 +582,15 @@ export function integrate(params: IntegrateParams): IntegratedResult {
540
582
  });
541
583
  }
542
584
 
585
+ if (hasSubscription) {
586
+ typeExtensions.push({
587
+ targetTypeName: "Subscription",
588
+ fields: resolversResult.subscriptionFields.fields.map(
589
+ convertToExtensionField,
590
+ ),
591
+ });
592
+ }
593
+
543
594
  for (const ext of resolversResult.typeExtensions) {
544
595
  if (!knownTypeNames.has(ext.targetTypeName)) {
545
596
  const firstField = ext.fields[0];
@@ -705,13 +756,15 @@ export function integrate(params: IntegrateParams): IntegratedResult {
705
756
  directiveDefinitions && directiveDefinitions.length > 0
706
757
  ? directiveDefinitions
707
758
  : null,
708
- abstractTypeResolvers: resolversResult.abstractTypeResolvers,
759
+ abstractTypeResolvers: remappedAbstractResolvers,
709
760
  autoGeneratedUnions,
710
761
  typenameAutoResolveTypes: typenameAutoResolveTypes ?? [],
762
+ discriminatorResolveTypes: discriminatorResolveTypes ?? [],
711
763
  numericEnums,
712
764
  stringEnumMappings,
713
765
  hasQuery,
714
766
  hasMutation,
767
+ hasSubscription,
715
768
  hasErrors,
716
769
  diagnostics,
717
770
  };
@@ -1,3 +1,4 @@
1
+ import type { DiscriminatorResolveTypeInfo } from "../../auto-type-generator/discriminator-resolve-type-generator.js";
1
2
  import type { AutoGeneratedResolveType } from "../../auto-type-generator/resolve-type-generator.js";
2
3
  import type { AbstractResolverInfo } from "../../resolver-extractor/extractor/define-api-extractor.js";
3
4
  import type {
@@ -30,6 +31,7 @@ export interface ResolverInfo {
30
31
  readonly sourceFiles: ReadonlyArray<string>;
31
32
  readonly abstractTypeResolvers: ReadonlyArray<AbstractTypeResolverInfo>;
32
33
  readonly autoGeneratedResolveTypes: ReadonlyArray<AutoGeneratedResolveType>;
34
+ readonly discriminatorResolveTypes: ReadonlyArray<DiscriminatorResolveTypeInfo>;
33
35
  }
34
36
 
35
37
  function getResolverValueName(typeName: string): string {
@@ -148,10 +150,15 @@ export function collectResolverInfo(
148
150
 
149
151
  const sourceFiles = [...sourceFilesSet].sort((a, b) => a.localeCompare(b));
150
152
 
153
+ const discriminatorResolveTypes = [
154
+ ...integratedResult.discriminatorResolveTypes,
155
+ ].sort((a, b) => a.unionTypeName.localeCompare(b.unionTypeName));
156
+
151
157
  return {
152
158
  types,
153
159
  sourceFiles,
154
160
  abstractTypeResolvers,
155
161
  autoGeneratedResolveTypes,
162
+ discriminatorResolveTypes,
156
163
  };
157
164
  }
@@ -39,6 +39,19 @@ const TS_ANONYMOUS_TYPE_SYMBOL = "__type";
39
39
  */
40
40
  const TS_ARRAY_TYPE_SYMBOL = "Array";
41
41
 
42
+ /**
43
+ * Object literal type symbol name.
44
+ *
45
+ * TypeScript uses `__object` as the internal symbol name for object literal
46
+ * expressions (value-space). This differs from `__type` which is used for
47
+ * anonymous object types in type-space.
48
+ *
49
+ * This commonly occurs when a type is derived via `typeof` from a variable
50
+ * initialized with an object literal, especially when the value comes from
51
+ * an external library.
52
+ */
53
+ const TS_OBJECT_LITERAL_TYPE_SYMBOL = "__object";
54
+
42
55
  /**
43
56
  * Checks if a symbol name represents a TypeScript internal/anonymous type
44
57
  * that should not be used directly as a GraphQL type name.
@@ -49,7 +62,8 @@ const TS_ARRAY_TYPE_SYMBOL = "Array";
49
62
  export function isInternalTypeSymbol(symbolName: string): boolean {
50
63
  return (
51
64
  symbolName === TS_ANONYMOUS_TYPE_SYMBOL ||
52
- symbolName === TS_ARRAY_TYPE_SYMBOL
65
+ symbolName === TS_ARRAY_TYPE_SYMBOL ||
66
+ symbolName === TS_OBJECT_LITERAL_TYPE_SYMBOL
53
67
  );
54
68
  }
55
69
 
@@ -32,6 +32,86 @@ export function buildEnumPrefixCandidate(enumName: string): string {
32
32
  return `${toUpperSnakeCase(enumName)}_`;
33
33
  }
34
34
 
35
+ const IRREGULAR_PLURAL_SEGMENTS = new Map([
36
+ ["ALIAS", "ALIASES"],
37
+ ["ANALYSIS", "ANALYSES"],
38
+ ["CHILD", "CHILDREN"],
39
+ ["COOKIE", "COOKIES"],
40
+ ["CRISIS", "CRISES"],
41
+ ["DIAGNOSIS", "DIAGNOSES"],
42
+ ["FOOT", "FEET"],
43
+ ["GOOSE", "GEESE"],
44
+ ["MAN", "MEN"],
45
+ ["MOUSE", "MICE"],
46
+ ["MOVIE", "MOVIES"],
47
+ ["PERSON", "PEOPLE"],
48
+ ["THESIS", "THESES"],
49
+ ["TOOTH", "TEETH"],
50
+ ["WOMAN", "WOMEN"],
51
+ ]);
52
+
53
+ function isConsonant(char: string): boolean {
54
+ return /^[BCDFGHJKLMNPQRSTVWXYZ]$/.test(char);
55
+ }
56
+
57
+ function pluralizeUpperSnakeSegment(segment: string): string {
58
+ const irregularPlural = IRREGULAR_PLURAL_SEGMENTS.get(segment);
59
+ if (irregularPlural) {
60
+ return irregularPlural;
61
+ }
62
+
63
+ if (
64
+ segment.endsWith("Y") &&
65
+ segment.length > 1 &&
66
+ isConsonant(segment.at(-2) ?? "")
67
+ ) {
68
+ return `${segment.slice(0, -1)}IES`;
69
+ }
70
+
71
+ if (
72
+ segment.endsWith("S") ||
73
+ segment.endsWith("SH") ||
74
+ segment.endsWith("CH") ||
75
+ segment.endsWith("X") ||
76
+ segment.endsWith("Z")
77
+ ) {
78
+ return `${segment}ES`;
79
+ }
80
+
81
+ return `${segment}S`;
82
+ }
83
+
84
+ function buildEnumPrefixCandidates(enumName: string): string[] {
85
+ const upperSnakeName = toUpperSnakeCase(enumName);
86
+ const baseCandidate = `${upperSnakeName}_`;
87
+ const segments = upperSnakeName.split("_");
88
+ const candidateSegmentSets: string[][] = [segments];
89
+
90
+ if (segments.length === 0) {
91
+ return [baseCandidate];
92
+ }
93
+
94
+ for (const [index, segment] of segments.entries()) {
95
+ const pluralizedSegment = pluralizeUpperSnakeSegment(segment);
96
+ if (pluralizedSegment === segment) {
97
+ continue;
98
+ }
99
+
100
+ const existingCandidates = [...candidateSegmentSets];
101
+ for (const candidateSegments of existingCandidates) {
102
+ const nextCandidateSegments = [...candidateSegments];
103
+ nextCandidateSegments[index] = pluralizedSegment;
104
+ candidateSegmentSets.push(nextCandidateSegments);
105
+ }
106
+ }
107
+
108
+ return [
109
+ ...new Set(
110
+ candidateSegmentSets.map((candidate) => `${candidate.join("_")}_`),
111
+ ),
112
+ ];
113
+ }
114
+
35
115
  export interface DetectEnumPrefixParams {
36
116
  readonly enumName: string;
37
117
  readonly memberValues: ReadonlyArray<string>;
@@ -71,20 +151,28 @@ export function detectEnumPrefix(
71
151
  return { shouldStrip: false, prefix: null };
72
152
  }
73
153
 
74
- const prefixCandidate = buildEnumPrefixCandidate(enumName);
154
+ for (const prefixCandidate of buildEnumPrefixCandidates(enumName)) {
155
+ let matches = true;
156
+
157
+ for (const value of memberValues) {
158
+ if (!value.startsWith(prefixCandidate)) {
159
+ matches = false;
160
+ break;
161
+ }
75
162
 
76
- for (const value of memberValues) {
77
- if (!value.startsWith(prefixCandidate)) {
78
- return { shouldStrip: false, prefix: null };
163
+ const stripped = value.slice(prefixCandidate.length);
164
+ if (stripped === "") {
165
+ matches = false;
166
+ break;
167
+ }
79
168
  }
80
169
 
81
- const stripped = value.slice(prefixCandidate.length);
82
- if (stripped === "") {
83
- return { shouldStrip: false, prefix: null };
170
+ if (matches) {
171
+ return { shouldStrip: true, prefix: prefixCandidate };
84
172
  }
85
173
  }
86
174
 
87
- return { shouldStrip: true, prefix: prefixCandidate };
175
+ return { shouldStrip: false, prefix: null };
88
176
  }
89
177
 
90
178
  /**
@@ -12,7 +12,7 @@ export function isInlineObjectType(type: ts.Type): boolean {
12
12
  return false;
13
13
  }
14
14
  const symbolName = type.symbol.getName();
15
- if (symbolName !== "__type") {
15
+ if (symbolName !== "__type" && symbolName !== "__object") {
16
16
  return false;
17
17
  }
18
18
  if (!(type.flags & ts.TypeFlags.Object)) {