@gqlkit-ts/cli 0.2.0 → 0.4.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 (235) hide show
  1. package/README.md +143 -0
  2. package/dist/auto-type-generator/auto-type-generator.d.ts +10 -4
  3. package/dist/auto-type-generator/auto-type-generator.d.ts.map +1 -1
  4. package/dist/auto-type-generator/auto-type-generator.js +656 -146
  5. package/dist/auto-type-generator/auto-type-generator.js.map +1 -1
  6. package/dist/auto-type-generator/index.d.ts +8 -1
  7. package/dist/auto-type-generator/index.d.ts.map +1 -1
  8. package/dist/auto-type-generator/index.js +3 -0
  9. package/dist/auto-type-generator/index.js.map +1 -1
  10. package/dist/auto-type-generator/inline-enum-collector.d.ts +13 -5
  11. package/dist/auto-type-generator/inline-enum-collector.d.ts.map +1 -1
  12. package/dist/auto-type-generator/inline-enum-collector.js +107 -71
  13. package/dist/auto-type-generator/inline-enum-collector.js.map +1 -1
  14. package/dist/auto-type-generator/inline-object-traverser.d.ts +20 -0
  15. package/dist/auto-type-generator/inline-object-traverser.d.ts.map +1 -0
  16. package/dist/auto-type-generator/inline-object-traverser.js +22 -0
  17. package/dist/auto-type-generator/inline-object-traverser.js.map +1 -0
  18. package/dist/auto-type-generator/inline-union-collector.d.ts +29 -0
  19. package/dist/auto-type-generator/inline-union-collector.d.ts.map +1 -0
  20. package/dist/auto-type-generator/inline-union-collector.js +216 -0
  21. package/dist/auto-type-generator/inline-union-collector.js.map +1 -0
  22. package/dist/auto-type-generator/inline-union-types.d.ts +29 -0
  23. package/dist/auto-type-generator/inline-union-types.d.ts.map +1 -0
  24. package/dist/auto-type-generator/inline-union-types.js +2 -0
  25. package/dist/auto-type-generator/inline-union-types.js.map +1 -0
  26. package/dist/auto-type-generator/inline-union-validator.d.ts +76 -0
  27. package/dist/auto-type-generator/inline-union-validator.d.ts.map +1 -0
  28. package/dist/auto-type-generator/inline-union-validator.js +329 -0
  29. package/dist/auto-type-generator/inline-union-validator.js.map +1 -0
  30. package/dist/auto-type-generator/naming-convention.d.ts +18 -1
  31. package/dist/auto-type-generator/naming-convention.d.ts.map +1 -1
  32. package/dist/auto-type-generator/naming-convention.js +16 -0
  33. package/dist/auto-type-generator/naming-convention.js.map +1 -1
  34. package/dist/auto-type-generator/resolve-type-generator.d.ts +20 -0
  35. package/dist/auto-type-generator/resolve-type-generator.d.ts.map +1 -0
  36. package/dist/auto-type-generator/resolve-type-generator.js +2 -0
  37. package/dist/auto-type-generator/resolve-type-generator.js.map +1 -0
  38. package/dist/auto-type-generator/resolver-field-iterator.d.ts +13 -0
  39. package/dist/auto-type-generator/resolver-field-iterator.d.ts.map +1 -0
  40. package/dist/auto-type-generator/resolver-field-iterator.js +22 -0
  41. package/dist/auto-type-generator/resolver-field-iterator.js.map +1 -0
  42. package/dist/auto-type-generator/typename-extractor.d.ts +26 -0
  43. package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -0
  44. package/dist/auto-type-generator/typename-extractor.js +142 -0
  45. package/dist/auto-type-generator/typename-extractor.js.map +1 -0
  46. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +35 -0
  47. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -0
  48. package/dist/auto-type-generator/typename-resolve-type-generator.js +177 -0
  49. package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -0
  50. package/dist/auto-type-generator/typename-types.d.ts +43 -0
  51. package/dist/auto-type-generator/typename-types.d.ts.map +1 -0
  52. package/dist/auto-type-generator/typename-types.js +37 -0
  53. package/dist/auto-type-generator/typename-types.js.map +1 -0
  54. package/dist/auto-type-generator/typename-validator.d.ts +37 -0
  55. package/dist/auto-type-generator/typename-validator.d.ts.map +1 -0
  56. package/dist/auto-type-generator/typename-validator.js +206 -0
  57. package/dist/auto-type-generator/typename-validator.js.map +1 -0
  58. package/dist/cli.js +2 -0
  59. package/dist/cli.js.map +1 -1
  60. package/dist/commands/docs.d.ts +51 -0
  61. package/dist/commands/docs.d.ts.map +1 -0
  62. package/dist/commands/docs.js +154 -0
  63. package/dist/commands/docs.js.map +1 -0
  64. package/dist/config/types.d.ts +13 -0
  65. package/dist/config/types.d.ts.map +1 -1
  66. package/dist/config-loader/loader.d.ts +3 -0
  67. package/dist/config-loader/loader.d.ts.map +1 -1
  68. package/dist/config-loader/loader.js +1 -0
  69. package/dist/config-loader/loader.js.map +1 -1
  70. package/dist/config-loader/validator.d.ts.map +1 -1
  71. package/dist/config-loader/validator.js +23 -0
  72. package/dist/config-loader/validator.js.map +1 -1
  73. package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
  74. package/dist/gen-orchestrator/orchestrator.js +32 -12
  75. package/dist/gen-orchestrator/orchestrator.js.map +1 -1
  76. package/dist/resolver-extractor/extract-resolvers.d.ts +19 -1
  77. package/dist/resolver-extractor/extract-resolvers.d.ts.map +1 -1
  78. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts +5 -0
  79. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
  80. package/dist/resolver-extractor/extractor/define-api-extractor.js +18 -65
  81. package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
  82. package/dist/resolver-extractor/index.d.ts +0 -1
  83. package/dist/resolver-extractor/index.d.ts.map +1 -1
  84. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +1 -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 +9 -5
  87. package/dist/resolver-extractor/validator/abstract-resolver-validator.js.map +1 -1
  88. package/dist/schema-generator/builder/ast-builder.d.ts +2 -2
  89. package/dist/schema-generator/builder/ast-builder.d.ts.map +1 -1
  90. package/dist/schema-generator/builder/ast-builder.js +12 -34
  91. package/dist/schema-generator/builder/ast-builder.js.map +1 -1
  92. package/dist/schema-generator/emitter/code-emitter.d.ts +3 -1
  93. package/dist/schema-generator/emitter/code-emitter.d.ts.map +1 -1
  94. package/dist/schema-generator/emitter/code-emitter.js +42 -12
  95. package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
  96. package/dist/schema-generator/emitter/sdl-emitter.d.ts +0 -4
  97. package/dist/schema-generator/emitter/sdl-emitter.d.ts.map +1 -1
  98. package/dist/schema-generator/emitter/sdl-emitter.js +0 -4
  99. package/dist/schema-generator/emitter/sdl-emitter.js.map +1 -1
  100. package/dist/schema-generator/generate-schema.d.ts +3 -0
  101. package/dist/schema-generator/generate-schema.d.ts.map +1 -1
  102. package/dist/schema-generator/generate-schema.js +83 -5
  103. package/dist/schema-generator/generate-schema.js.map +1 -1
  104. package/dist/schema-generator/integrator/result-integrator.d.ts +19 -9
  105. package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
  106. package/dist/schema-generator/integrator/result-integrator.js +60 -44
  107. package/dist/schema-generator/integrator/result-integrator.js.map +1 -1
  108. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts +2 -0
  109. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts.map +1 -1
  110. package/dist/schema-generator/resolver-collector/resolver-collector.js +22 -0
  111. package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
  112. package/dist/shared/branded-type-detector.d.ts +43 -0
  113. package/dist/shared/branded-type-detector.d.ts.map +1 -0
  114. package/dist/shared/branded-type-detector.js +146 -0
  115. package/dist/shared/branded-type-detector.js.map +1 -0
  116. package/dist/shared/enum-prefix-detector.d.ts +63 -0
  117. package/dist/shared/enum-prefix-detector.d.ts.map +1 -0
  118. package/dist/shared/enum-prefix-detector.js +80 -0
  119. package/dist/shared/enum-prefix-detector.js.map +1 -0
  120. package/dist/shared/ignore-fields-detector.d.ts +26 -0
  121. package/dist/shared/ignore-fields-detector.d.ts.map +1 -0
  122. package/dist/shared/ignore-fields-detector.js +83 -0
  123. package/dist/shared/ignore-fields-detector.js.map +1 -0
  124. package/dist/shared/ignore-fields-validator.d.ts +29 -0
  125. package/dist/shared/ignore-fields-validator.d.ts.map +1 -0
  126. package/dist/shared/ignore-fields-validator.js +43 -0
  127. package/dist/shared/ignore-fields-validator.js.map +1 -0
  128. package/dist/shared/index.d.ts +2 -0
  129. package/dist/shared/index.d.ts.map +1 -1
  130. package/dist/shared/index.js.map +1 -1
  131. package/dist/shared/source-location.d.ts +5 -0
  132. package/dist/shared/source-location.d.ts.map +1 -1
  133. package/dist/shared/source-location.js +7 -0
  134. package/dist/shared/source-location.js.map +1 -1
  135. package/dist/shared/string-utils.d.ts +2 -0
  136. package/dist/shared/string-utils.d.ts.map +1 -0
  137. package/dist/shared/string-utils.js +8 -0
  138. package/dist/shared/string-utils.js.map +1 -0
  139. package/dist/type-extractor/converter/field-eligibility.d.ts +8 -6
  140. package/dist/type-extractor/converter/field-eligibility.d.ts.map +1 -1
  141. package/dist/type-extractor/converter/field-eligibility.js +7 -28
  142. package/dist/type-extractor/converter/field-eligibility.js.map +1 -1
  143. package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
  144. package/dist/type-extractor/converter/graphql-converter.js +27 -18
  145. package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
  146. package/dist/type-extractor/extractor/field-type-resolver.d.ts.map +1 -1
  147. package/dist/type-extractor/extractor/field-type-resolver.js +81 -7
  148. package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -1
  149. package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
  150. package/dist/type-extractor/extractor/type-extractor.js +88 -23
  151. package/dist/type-extractor/extractor/type-extractor.js.map +1 -1
  152. package/dist/type-extractor/types/diagnostics.d.ts +1 -1
  153. package/dist/type-extractor/types/diagnostics.d.ts.map +1 -1
  154. package/dist/type-extractor/types/ts-type-reference-factory.d.ts +10 -2
  155. package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -1
  156. package/dist/type-extractor/types/ts-type-reference-factory.js +8 -2
  157. package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -1
  158. package/dist/type-extractor/types/typescript.d.ts +4 -0
  159. package/dist/type-extractor/types/typescript.d.ts.map +1 -1
  160. package/dist/type-extractor/validator/type-validator.d.ts +1 -1
  161. package/dist/type-extractor/validator/type-validator.d.ts.map +1 -1
  162. package/dist/type-extractor/validator/type-validator.js +2 -10
  163. package/dist/type-extractor/validator/type-validator.js.map +1 -1
  164. package/docs/coding-agents.md +64 -0
  165. package/docs/configuration.md +15 -20
  166. package/docs/getting-started.md +15 -12
  167. package/docs/index.md +36 -22
  168. package/docs/integration/apollo.md +8 -40
  169. package/docs/integration/drizzle.md +6 -10
  170. package/docs/integration/prisma.md +196 -0
  171. package/docs/integration/yoga.md +8 -40
  172. package/docs/schema/abstract-resolvers.md +117 -0
  173. package/docs/schema/directives.md +5 -0
  174. package/docs/schema/documentation.md +5 -0
  175. package/docs/schema/enums.md +99 -0
  176. package/docs/schema/fields.md +64 -0
  177. package/docs/schema/index.md +21 -0
  178. package/docs/schema/inputs.md +115 -15
  179. package/docs/schema/interfaces.md +31 -1
  180. package/docs/schema/objects.md +40 -0
  181. package/docs/schema/queries-mutations.md +136 -22
  182. package/docs/schema/scalars.md +5 -0
  183. package/docs/schema/unions.md +208 -1
  184. package/docs/what-is-gqlkit.md +13 -8
  185. package/package.json +6 -4
  186. package/src/auto-type-generator/auto-type-generator.ts +969 -227
  187. package/src/auto-type-generator/index.ts +42 -0
  188. package/src/auto-type-generator/inline-enum-collector.ts +187 -139
  189. package/src/auto-type-generator/inline-object-traverser.ts +49 -0
  190. package/src/auto-type-generator/inline-union-collector.ts +402 -0
  191. package/src/auto-type-generator/inline-union-types.ts +33 -0
  192. package/src/auto-type-generator/inline-union-validator.ts +482 -0
  193. package/src/auto-type-generator/naming-convention.ts +38 -1
  194. package/src/auto-type-generator/resolve-type-generator.ts +21 -0
  195. package/src/auto-type-generator/resolver-field-iterator.ts +39 -0
  196. package/src/auto-type-generator/typename-extractor.ts +230 -0
  197. package/src/auto-type-generator/typename-resolve-type-generator.ts +281 -0
  198. package/src/auto-type-generator/typename-types.ts +66 -0
  199. package/src/auto-type-generator/typename-validator.ts +326 -0
  200. package/src/cli.ts +2 -0
  201. package/src/commands/docs.ts +211 -0
  202. package/src/config/types.ts +15 -0
  203. package/src/config-loader/loader.ts +4 -0
  204. package/src/config-loader/validator.ts +33 -0
  205. package/src/gen-orchestrator/orchestrator.ts +50 -17
  206. package/src/resolver-extractor/extract-resolvers.ts +19 -0
  207. package/src/resolver-extractor/extractor/define-api-extractor.ts +28 -94
  208. package/src/resolver-extractor/index.ts +0 -6
  209. package/src/resolver-extractor/validator/abstract-resolver-validator.ts +16 -8
  210. package/src/schema-generator/builder/ast-builder.ts +52 -81
  211. package/src/schema-generator/emitter/code-emitter.ts +82 -11
  212. package/src/schema-generator/emitter/sdl-emitter.ts +0 -4
  213. package/src/schema-generator/generate-schema.ts +109 -14
  214. package/src/schema-generator/integrator/result-integrator.ts +91 -63
  215. package/src/schema-generator/resolver-collector/resolver-collector.ts +34 -0
  216. package/src/shared/branded-type-detector.ts +182 -0
  217. package/src/shared/enum-prefix-detector.ts +99 -0
  218. package/src/shared/ignore-fields-detector.ts +109 -0
  219. package/src/shared/ignore-fields-validator.ts +66 -0
  220. package/src/shared/index.ts +2 -0
  221. package/src/shared/source-location.ts +11 -0
  222. package/src/shared/string-utils.ts +7 -0
  223. package/src/type-extractor/converter/field-eligibility.ts +13 -29
  224. package/src/type-extractor/converter/graphql-converter.ts +37 -23
  225. package/src/type-extractor/extractor/field-type-resolver.ts +97 -7
  226. package/src/type-extractor/extractor/type-extractor.ts +103 -26
  227. package/src/type-extractor/types/diagnostics.ts +13 -2
  228. package/src/type-extractor/types/ts-type-reference-factory.ts +18 -5
  229. package/src/type-extractor/types/typescript.ts +4 -0
  230. package/src/type-extractor/validator/type-validator.ts +2 -15
  231. package/dist/resolver-extractor/validator/only-validator.d.ts +0 -61
  232. package/dist/resolver-extractor/validator/only-validator.d.ts.map +0 -1
  233. package/dist/resolver-extractor/validator/only-validator.js +0 -76
  234. package/dist/resolver-extractor/validator/only-validator.js.map +0 -1
  235. package/src/resolver-extractor/validator/only-validator.ts +0 -158
@@ -0,0 +1,66 @@
1
+ /**
2
+ * IgnoreFields validator.
3
+ *
4
+ * This module provides validation functions for ignoreFields metadata,
5
+ * checking that specified field names exist in the type and that not
6
+ * all fields are excluded.
7
+ */
8
+
9
+ import type {
10
+ Diagnostic,
11
+ SourceLocation,
12
+ } from "../type-extractor/types/diagnostics.js";
13
+
14
+ /**
15
+ * Parameters for validateIgnoreFields function.
16
+ */
17
+ export interface ValidateIgnoreFieldsParams {
18
+ readonly typeName: string;
19
+ readonly ignoreFields: ReadonlySet<string>;
20
+ readonly allFieldNames: ReadonlySet<string>;
21
+ readonly sourceLocation: SourceLocation;
22
+ }
23
+
24
+ /**
25
+ * Validates ignoreFields metadata.
26
+ *
27
+ * This function checks:
28
+ * 1. All field names in ignoreFields exist in the type
29
+ * 2. Not all fields are excluded (at least one field must remain)
30
+ *
31
+ * @param params - The validation parameters
32
+ * @returns Array of diagnostics (empty if valid)
33
+ */
34
+ export function validateIgnoreFields(
35
+ params: ValidateIgnoreFieldsParams,
36
+ ): ReadonlyArray<Diagnostic> {
37
+ const { typeName, ignoreFields, allFieldNames, sourceLocation } = params;
38
+ const diagnostics: Diagnostic[] = [];
39
+
40
+ for (const fieldName of ignoreFields) {
41
+ if (!allFieldNames.has(fieldName)) {
42
+ const availableFields = [...allFieldNames].sort().join(", ");
43
+ diagnostics.push({
44
+ code: "IGNORE_FIELD_NOT_FOUND",
45
+ message: `Type '${typeName}': ignoreFields contains unknown field '${fieldName}'. Available fields: ${availableFields}`,
46
+ severity: "error",
47
+ location: sourceLocation,
48
+ });
49
+ }
50
+ }
51
+
52
+ const remainingFieldCount = [...allFieldNames].filter(
53
+ (f) => !ignoreFields.has(f),
54
+ ).length;
55
+
56
+ if (remainingFieldCount === 0 && allFieldNames.size > 0) {
57
+ diagnostics.push({
58
+ code: "IGNORE_ALL_FIELDS",
59
+ message: `Type '${typeName}': ignoreFields excludes all fields. At least one field must remain.`,
60
+ severity: "error",
61
+ location: sourceLocation,
62
+ });
63
+ }
64
+
65
+ return diagnostics;
66
+ }
@@ -26,6 +26,8 @@ export {
26
26
  type ScanResult,
27
27
  scanDirectory,
28
28
  } from "./file-scanner.js";
29
+ export type { DetectIgnoreFieldsParams } from "./ignore-fields-detector.js";
30
+ export type { ValidateIgnoreFieldsParams } from "./ignore-fields-validator.js";
29
31
  export type { TypeConverter } from "./inline-object-extractor.js";
30
32
  export { toPosixPath } from "./path-utils.js";
31
33
  export type { SourceLocation } from "./source-location.js";
@@ -25,3 +25,14 @@ export function getSourceLocationFromNode(
25
25
  column: character + 1,
26
26
  };
27
27
  }
28
+
29
+ /**
30
+ * Returns the source location if present, otherwise creates a default location
31
+ * with the given source file and line/column 1.
32
+ */
33
+ export function getSourceLocationOrDefault(
34
+ location: SourceLocation | null,
35
+ sourceFile: string,
36
+ ): SourceLocation {
37
+ return location ?? { file: sourceFile, line: 1, column: 1 };
38
+ }
@@ -0,0 +1,7 @@
1
+ export function toScreamingSnakeCase(value: string): string {
2
+ return value
3
+ .replace(/[-\s]+/g, "_")
4
+ .replace(/([a-z])([A-Z])/g, "$1_$2")
5
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
6
+ .toUpperCase();
7
+ }
@@ -24,45 +24,29 @@ function isReservedName(name: string): boolean {
24
24
  return name.startsWith("__");
25
25
  }
26
26
 
27
- /**
28
- * Check if a field name is eligible to be included as a GraphQL object field.
29
- */
30
- export function isEligibleAsObjectField(fieldName: string): EligibilityResult {
31
- if (isReservedName(fieldName)) {
32
- return {
33
- eligible: false,
34
- skipReason: {
35
- code: "RESERVED_NAME",
36
- message: `Field '${fieldName}' starts with '__' which is reserved for GraphQL introspection`,
37
- },
38
- };
39
- }
27
+ export type FieldEligibilityKind = "object" | "input";
40
28
 
41
- if (!isValidGraphQLName(fieldName)) {
42
- return {
43
- eligible: false,
44
- skipReason: {
45
- code: "INVALID_NAME",
46
- message: `Field '${fieldName}' is not a valid GraphQL identifier (must match /^[_A-Za-z][_0-9A-Za-z]*$/)`,
47
- },
48
- };
49
- }
50
-
51
- return { eligible: true, skipReason: null };
29
+ export interface IsEligibleFieldParams {
30
+ readonly fieldName: string;
31
+ readonly kind: FieldEligibilityKind;
52
32
  }
53
33
 
54
34
  /**
55
- * Check if a field name is eligible to be included as a GraphQL input object field.
35
+ * Check if a field name is eligible to be included as a GraphQL field.
36
+ * Uses the kind parameter to determine the error message prefix.
56
37
  */
57
- export function isEligibleAsInputObjectField(
58
- fieldName: string,
38
+ export function isEligibleField(
39
+ params: IsEligibleFieldParams,
59
40
  ): EligibilityResult {
41
+ const { fieldName, kind } = params;
42
+ const prefix = kind === "input" ? "Input field" : "Field";
43
+
60
44
  if (isReservedName(fieldName)) {
61
45
  return {
62
46
  eligible: false,
63
47
  skipReason: {
64
48
  code: "RESERVED_NAME",
65
- message: `Input field '${fieldName}' starts with '__' which is reserved for GraphQL introspection`,
49
+ message: `${prefix} '${fieldName}' starts with '__' which is reserved for GraphQL introspection`,
66
50
  },
67
51
  };
68
52
  }
@@ -72,7 +56,7 @@ export function isEligibleAsInputObjectField(
72
56
  eligible: false,
73
57
  skipReason: {
74
58
  code: "INVALID_NAME",
75
- message: `Input field '${fieldName}' is not a valid GraphQL identifier (must match /^[_A-Za-z][_0-9A-Za-z]*$/)`,
59
+ message: `${prefix} '${fieldName}' is not a valid GraphQL identifier (must match /^[_A-Za-z][_0-9A-Za-z]*$/)`,
76
60
  },
77
61
  };
78
62
  }
@@ -3,6 +3,11 @@ import {
3
3
  isBuiltInScalar,
4
4
  PRIMITIVE_TYPE_MAP,
5
5
  } from "../../shared/constants.js";
6
+ import {
7
+ detectEnumPrefix,
8
+ stripEnumPrefix,
9
+ } from "../../shared/enum-prefix-detector.js";
10
+ import { toScreamingSnakeCase } from "../../shared/string-utils.js";
6
11
  import { convertTsTypeToGraphQLType } from "../../shared/type-converter.js";
7
12
  import type {
8
13
  Diagnostic,
@@ -15,11 +20,7 @@ import type {
15
20
  InlineObjectProperty,
16
21
  SourceLocation,
17
22
  } from "../types/index.js";
18
- import {
19
- isEligibleAsEnumValue,
20
- isEligibleAsInputObjectField,
21
- isEligibleAsObjectField,
22
- } from "./field-eligibility.js";
23
+ import { isEligibleAsEnumValue, isEligibleField } from "./field-eligibility.js";
23
24
 
24
25
  export interface ConversionResult {
25
26
  readonly types: ReadonlyArray<GraphQLTypeInfo>;
@@ -37,14 +38,6 @@ function isInputTypeName(name: string): boolean {
37
38
  return name.endsWith("Input");
38
39
  }
39
40
 
40
- function toScreamingSnakeCase(value: string): string {
41
- return value
42
- .replace(/[-\s]+/g, "_")
43
- .replace(/([a-z])([A-Z])/g, "$1_$2")
44
- .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
45
- .toUpperCase();
46
- }
47
-
48
41
  interface ConvertEnumMembersParams {
49
42
  readonly members: ReadonlyArray<EnumMemberInfo>;
50
43
  readonly enumName: string;
@@ -60,6 +53,11 @@ interface ConvertEnumMembersResult {
60
53
  readonly hasError: boolean;
61
54
  }
62
55
 
56
+ interface ConvertedMemberInfo {
57
+ readonly convertedName: string;
58
+ readonly member: EnumMemberInfo;
59
+ }
60
+
63
61
  function convertEnumMembers(
64
62
  params: ConvertEnumMembersParams,
65
63
  ): ConvertEnumMembersResult {
@@ -69,7 +67,7 @@ function convertEnumMembers(
69
67
  let isNumeric = false;
70
68
  let needsMapping = false;
71
69
 
72
- const convertedNameToOriginals = new Map<string, string[]>();
70
+ const eligibleMembers: ConvertedMemberInfo[] = [];
73
71
 
74
72
  for (const member of members) {
75
73
  const convertedName = toScreamingSnakeCase(member.name);
@@ -93,16 +91,31 @@ function convertEnumMembers(
93
91
  isNumeric = true;
94
92
  }
95
93
 
96
- if (convertedName !== member.value) {
94
+ eligibleMembers.push({ convertedName, member });
95
+ }
96
+
97
+ const prefixDetectionResult = detectEnumPrefix({
98
+ enumName,
99
+ memberValues: eligibleMembers.map((m) => m.convertedName),
100
+ });
101
+
102
+ const finalNameToOriginals = new Map<string, string[]>();
103
+
104
+ for (const { convertedName, member } of eligibleMembers) {
105
+ let finalName = convertedName;
106
+ if (prefixDetectionResult.shouldStrip && prefixDetectionResult.prefix) {
107
+ finalName = stripEnumPrefix(convertedName, prefixDetectionResult.prefix);
108
+ needsMapping = true;
109
+ } else if (convertedName !== member.value) {
97
110
  needsMapping = true;
98
111
  }
99
112
 
100
- const existingOriginals = convertedNameToOriginals.get(convertedName) ?? [];
113
+ const existingOriginals = finalNameToOriginals.get(finalName) ?? [];
101
114
  existingOriginals.push(member.value);
102
- convertedNameToOriginals.set(convertedName, existingOriginals);
115
+ finalNameToOriginals.set(finalName, existingOriginals);
103
116
 
104
117
  values.push({
105
- name: convertedName,
118
+ name: finalName,
106
119
  originalValue: member.value,
107
120
  numericValue: member.numericValue,
108
121
  description: member.description,
@@ -111,11 +124,11 @@ function convertEnumMembers(
111
124
  }
112
125
 
113
126
  let hasError = false;
114
- for (const [convertedName, originals] of convertedNameToOriginals) {
127
+ for (const [finalName, originals] of finalNameToOriginals) {
115
128
  if (originals.length > 1) {
116
129
  diagnostics.push({
117
130
  code: "DUPLICATE_ENUM_VALUE_AFTER_CONVERSION",
118
- message: `Enum '${enumName}' has duplicate value '${convertedName}' after conversion (from '${originals.join("' and '")}')`,
131
+ message: `Enum '${enumName}' has duplicate value '${finalName}' after conversion (from '${originals.join("' and '")}')`,
119
132
  severity: "error",
120
133
  location: enumLocation,
121
134
  });
@@ -139,9 +152,10 @@ function convertFields(
139
152
  const diagnostics: Diagnostic[] = [];
140
153
 
141
154
  for (const field of extracted.fields) {
142
- const eligibility = isInput
143
- ? isEligibleAsInputObjectField(field.name)
144
- : isEligibleAsObjectField(field.name);
155
+ const eligibility = isEligibleField({
156
+ fieldName: field.name,
157
+ kind: isInput ? "input" : "object",
158
+ });
145
159
 
146
160
  if (!eligibility.eligible) {
147
161
  diagnostics.push({
@@ -1,4 +1,8 @@
1
1
  import ts from "typescript";
2
+ import {
3
+ detectBrandedType,
4
+ detectUniformBrandedType,
5
+ } from "../../shared/branded-type-detector.js";
2
6
  import { isInternalTypeSymbol } from "../../shared/constants.js";
3
7
  import { extractInlineObjectProperties as extractInlineObjectPropertiesShared } from "../../shared/inline-object-extractor.js";
4
8
  import { isInlineObjectType } from "../../shared/inline-object-utils.js";
@@ -175,6 +179,20 @@ function resolveFieldTypeInternal(
175
179
  });
176
180
  }
177
181
 
182
+ // Check if all non-null types are branded primitives with the same base type
183
+ // This handles cases like: boolean & { __nominal: true }
184
+ // which expands to: (true & { __nominal: true }) | (false & { __nominal: true })
185
+ const uniformBrandedResult = detectUniformBrandedType(nonNullTypes);
186
+ if (
187
+ uniformBrandedResult.isBranded &&
188
+ uniformBrandedResult.baseType !== null
189
+ ) {
190
+ return createPrimitiveType({
191
+ name: uniformBrandedResult.baseType,
192
+ nullable,
193
+ });
194
+ }
195
+
178
196
  if (nonNullTypes.length === 1) {
179
197
  const nonNullTypeNode =
180
198
  typeNode && ts.isUnionTypeNode(typeNode)
@@ -235,18 +253,45 @@ function resolveFieldTypeInternal(
235
253
  return createLiteralType(typeString);
236
254
  }
237
255
 
238
- // Intersection types in field context are ALWAYS treated as inline objects
239
- // GraphQL doesn't have intersection types, so we must expand them
256
+ // Intersection types in field context
257
+ // GraphQL doesn't have intersection types, so we must resolve them appropriately
240
258
  if (type.isIntersection()) {
241
- // If the intersection has an alias that's in knownTypeNames, use it
259
+ // 1. If the intersection has an alias that's in knownTypeNames, use it as reference
242
260
  if (type.aliasSymbol) {
243
261
  const aliasName = type.aliasSymbol.getName();
244
262
  if (isKnownSchemaType(aliasName, type.aliasSymbol, ctx)) {
245
263
  return createReferenceType({ name: aliasName, nullable: false });
246
264
  }
265
+
266
+ // 2. Check if aliasSymbol has a globalTypeMapping (custom scalar)
267
+ const globalMapping = globalTypeMappings.find(
268
+ (m) => m.typeName === aliasName,
269
+ );
270
+ if (globalMapping) {
271
+ return createScalarType({
272
+ name: globalMapping.scalarName,
273
+ scalarInfo: {
274
+ scalarName: globalMapping.scalarName,
275
+ typeName: globalMapping.typeName,
276
+ baseType: undefined,
277
+ isCustom: true,
278
+ only: globalMapping.only,
279
+ },
280
+ nullable: false,
281
+ });
282
+ }
283
+ }
284
+
285
+ // 3. Check if this is a branded primitive type pattern
286
+ const brandedResult = detectBrandedType(type);
287
+ if (brandedResult.isBranded && brandedResult.baseType !== null) {
288
+ return createPrimitiveType({
289
+ name: brandedResult.baseType,
290
+ nullable: false,
291
+ });
247
292
  }
248
293
 
249
- // Otherwise, treat as inline object
294
+ // 4. Otherwise, treat as inline object
250
295
  return tryExtractAsInlineObject(type, ctx);
251
296
  }
252
297
 
@@ -287,6 +332,28 @@ function resolveFieldTypeInternal(
287
332
  }
288
333
  }
289
334
 
335
+ // Type alias expansion: type aliases not in knownTypeNames should be expanded as inline objects
336
+ // This handles cases like: type MyPayload = { user: User; success: boolean; }
337
+ // where MyPayload is used as return type but not declared as a schema type
338
+ // Only expand if the underlying type is an anonymous object literal, not a named type
339
+ // IMPORTANT: Only expand if the name doesn't exist in schema at all.
340
+ // If the name exists but symbols don't match, that's a shadowing case handled by later logic.
341
+ if (type.aliasSymbol) {
342
+ const aliasName = type.aliasSymbol.getName();
343
+ if (!knownTypeNames.has(aliasName)) {
344
+ // Check if this is an anonymous object type (not an interface or another named type)
345
+ // using ts.ObjectFlags.Anonymous for a more robust check than internal symbol names
346
+ const isAnonymousObject =
347
+ (type.flags & ts.TypeFlags.Object) !== 0 &&
348
+ ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) !== 0;
349
+
350
+ if (isAnonymousObject) {
351
+ // Not a known schema type and is an anonymous object - expand to generate Payload type
352
+ return tryExtractAsInlineObject(type, ctx);
353
+ }
354
+ }
355
+ }
356
+
290
357
  // Extract type name from typeNode first (takes precedence over type.symbol).
291
358
  // This handles cases like:
292
359
  // - `typeof def` where the type's symbol is internal (__type, __object)
@@ -406,7 +473,7 @@ function tryExtractAsInlineObject(
406
473
  type: ts.Type,
407
474
  ctx: InternalFieldTypeContext,
408
475
  ): TSTypeReference {
409
- const { visitedTypes } = ctx;
476
+ const { visitedTypes, checker } = ctx;
410
477
  if (visitedTypes.has(type)) {
411
478
  // Cycle detected, return a placeholder reference
412
479
  const typeName = type.symbol?.getName() ?? "Unknown";
@@ -417,11 +484,34 @@ function tryExtractAsInlineObject(
417
484
 
418
485
  const inlineProperties = extractInlineObjectPropertiesShared(
419
486
  type,
420
- ctx.checker,
487
+ checker,
421
488
  (propType) => resolveFieldTypeInternal(propType, undefined, ctx),
422
489
  );
423
490
 
424
- return createInlineObjectType(inlineProperties);
491
+ // Extract type-level TSDoc from the alias symbol if present (Requirement 7.2)
492
+ // Only extract from user-defined types, not built-in TypeScript utility types
493
+ let description: string | null = null;
494
+ let deprecated: DeprecationInfo | null = null;
495
+ if (type.aliasSymbol) {
496
+ const declarations = type.aliasSymbol.getDeclarations();
497
+ const isUserDefined =
498
+ declarations?.some((decl) => {
499
+ const sourceFile = decl.getSourceFile();
500
+ return !sourceFile.isDeclarationFile;
501
+ }) ?? false;
502
+
503
+ if (isUserDefined) {
504
+ const tsdocInfo = extractTsDocFromSymbol(type.aliasSymbol, checker);
505
+ description = tsdocInfo.description;
506
+ deprecated = tsdocInfo.deprecated;
507
+ }
508
+ }
509
+
510
+ return createInlineObjectType({
511
+ properties: inlineProperties,
512
+ description,
513
+ deprecated,
514
+ });
425
515
  }
426
516
 
427
517
  /**
@@ -9,6 +9,8 @@ import {
9
9
  hasDirectiveMetadata,
10
10
  unwrapDirectiveType,
11
11
  } from "../../shared/directive-detector.js";
12
+ import { detectIgnoreFieldsMetadata } from "../../shared/ignore-fields-detector.js";
13
+ import { validateIgnoreFields } from "../../shared/ignore-fields-validator.js";
12
14
  import { extractInlineObjectProperties as extractInlineObjectPropertiesShared } from "../../shared/inline-object-extractor.js";
13
15
  import { isInlineObjectType } from "../../shared/inline-object-utils.js";
14
16
  import {
@@ -157,7 +159,11 @@ function tryExtractAsInlineObject(
157
159
  (t) => convertTsTypeToReference(t, ctx).tsType,
158
160
  );
159
161
  return {
160
- tsType: createInlineObjectType(inlineProperties),
162
+ tsType: createInlineObjectType({
163
+ properties: inlineProperties,
164
+ description: null,
165
+ deprecated: null,
166
+ }),
161
167
  };
162
168
  }
163
169
 
@@ -404,6 +410,21 @@ interface FieldExtractionResult {
404
410
  diagnostics: Diagnostic[];
405
411
  }
406
412
 
413
+ function collectAllFieldNames(
414
+ type: ts.Type,
415
+ checker: ts.TypeChecker,
416
+ ): ReadonlySet<string> {
417
+ const properties = extractPropertySymbols(type, checker);
418
+ const fieldNames = new Set<string>();
419
+ for (const prop of properties) {
420
+ const propName = prop.getName();
421
+ if (!propName.startsWith(" $")) {
422
+ fieldNames.add(propName);
423
+ }
424
+ }
425
+ return fieldNames;
426
+ }
427
+
407
428
  interface ExtractFieldsParams {
408
429
  readonly type: ts.Type;
409
430
  readonly checker: ts.TypeChecker;
@@ -414,6 +435,7 @@ interface ExtractFieldsParams {
414
435
  readonly sourceFiles: ReadonlySet<string>;
415
436
  readonly scalarMappingTable: ScalarBaseTypeMappingTable | null;
416
437
  readonly scalarMappingContext: ScalarMappingContext;
438
+ readonly ignoreFields: ReadonlySet<string> | null;
417
439
  }
418
440
 
419
441
  function extractFieldsFromType(
@@ -429,6 +451,7 @@ function extractFieldsFromType(
429
451
  sourceFiles,
430
452
  scalarMappingTable,
431
453
  scalarMappingContext,
454
+ ignoreFields,
432
455
  } = params;
433
456
  const fields: FieldDefinition[] = [];
434
457
  const diagnostics: Diagnostic[] = [];
@@ -441,6 +464,10 @@ function extractFieldsFromType(
441
464
  continue;
442
465
  }
443
466
 
467
+ if (ignoreFields?.has(propName)) {
468
+ continue;
469
+ }
470
+
444
471
  const propType = checker.getTypeOfSymbol(prop);
445
472
  const declarations = prop.getDeclarations();
446
473
  const declaration = declarations?.[0];
@@ -945,6 +972,19 @@ function processReexportedSymbol(
945
972
  ? reexportDeclaration.type
946
973
  : undefined;
947
974
  const unionMembers = extractUnionMembers(type, reexportTypeNode);
975
+ const ignoreFields = detectIgnoreFieldsMetadata({ type, checker });
976
+
977
+ if (ignoreFields !== null && kind !== "union") {
978
+ const allFieldNames = collectAllFieldNames(type, checker);
979
+ const validationDiagnostics = validateIgnoreFields({
980
+ typeName: exportedName,
981
+ ignoreFields,
982
+ allFieldNames,
983
+ sourceLocation: location,
984
+ });
985
+ diagnostics.push(...validationDiagnostics);
986
+ }
987
+
948
988
  const fieldResult =
949
989
  kind === "union"
950
990
  ? { fields: [], diagnostics: [] }
@@ -958,6 +998,7 @@ function processReexportedSymbol(
958
998
  sourceFiles: scannedSourceFiles,
959
999
  scalarMappingTable,
960
1000
  scalarMappingContext,
1001
+ ignoreFields,
961
1002
  });
962
1003
  diagnostics.push(...fieldResult.diagnostics);
963
1004
 
@@ -1130,17 +1171,23 @@ interface ExtractInlineObjectMembersParams {
1130
1171
  readonly checker: ts.TypeChecker;
1131
1172
  readonly globalTypeMappings: ReadonlyArray<GlobalTypeMapping>;
1132
1173
  readonly knownTypeNames: ReadonlySet<string>;
1174
+ readonly typeNode: ts.TypeNode | undefined;
1133
1175
  }
1134
1176
 
1135
1177
  function extractInlineObjectMembers(
1136
1178
  params: ExtractInlineObjectMembersParams,
1137
1179
  ): InlineObjectExtractionResult | null {
1138
- const { type, checker, globalTypeMappings, knownTypeNames } = params;
1180
+ const { type, checker, globalTypeMappings, knownTypeNames, typeNode } =
1181
+ params;
1139
1182
  if (!type.isUnion()) {
1140
1183
  return null;
1141
1184
  }
1142
1185
 
1143
1186
  const nonNullTypes = getNonNullableTypes(type);
1187
+ const memberTypeNodes =
1188
+ typeNode && ts.isUnionTypeNode(typeNode)
1189
+ ? filterNonNullTypeNodes(typeNode)
1190
+ : [];
1144
1191
 
1145
1192
  const allObjectTypes = nonNullTypes.every(
1146
1193
  (t) =>
@@ -1163,28 +1210,45 @@ function extractInlineObjectMembers(
1163
1210
  visitedTypes: new WeakSet(),
1164
1211
  };
1165
1212
 
1166
- for (const memberType of nonNullTypes) {
1167
- if (isAnonymousObjectType(memberType)) {
1168
- hasInlineObjects = true;
1169
- const properties = memberType.getProperties();
1170
- const memberProperties: InlineObjectProperty[] = [];
1213
+ if (memberTypeNodes.length > 0) {
1214
+ for (const memberNode of memberTypeNodes) {
1215
+ if (ts.isTypeReferenceNode(memberNode)) {
1216
+ hasNamedTypes = true;
1217
+ } else {
1218
+ hasInlineObjects = true;
1219
+ }
1220
+ }
1221
+ } else {
1222
+ for (const memberType of nonNullTypes) {
1223
+ if (isAnonymousObjectType(memberType)) {
1224
+ hasInlineObjects = true;
1225
+ } else {
1226
+ hasNamedTypes = true;
1227
+ }
1228
+ }
1229
+ }
1230
+
1231
+ if (hasInlineObjects) {
1232
+ for (const memberType of nonNullTypes) {
1233
+ if (isAnonymousObjectType(memberType)) {
1234
+ const properties = memberType.getProperties();
1235
+ const memberProperties: InlineObjectProperty[] = [];
1171
1236
 
1172
- for (const prop of properties) {
1173
- const propType = checker.getTypeOfSymbol(prop);
1174
- const tsdocInfo = extractTsDocFromSymbol(prop, checker);
1175
- const typeResult = convertTsTypeToReference(propType, ctx);
1237
+ for (const prop of properties) {
1238
+ const propType = checker.getTypeOfSymbol(prop);
1239
+ const tsdocInfo = extractTsDocFromSymbol(prop, checker);
1240
+ const typeResult = convertTsTypeToReference(propType, ctx);
1176
1241
 
1177
- memberProperties.push({
1178
- propertyName: prop.getName(),
1179
- propertyType: typeResult.tsType,
1180
- description: tsdocInfo.description ?? null,
1181
- deprecated: tsdocInfo.deprecated ?? null,
1182
- });
1183
- }
1242
+ memberProperties.push({
1243
+ propertyName: prop.getName(),
1244
+ propertyType: typeResult.tsType,
1245
+ description: tsdocInfo.description ?? null,
1246
+ deprecated: tsdocInfo.deprecated ?? null,
1247
+ });
1248
+ }
1184
1249
 
1185
- members.push({ properties: memberProperties });
1186
- } else {
1187
- hasNamedTypes = true;
1250
+ members.push({ properties: memberProperties });
1251
+ }
1188
1252
  }
1189
1253
  }
1190
1254
 
@@ -1415,6 +1479,7 @@ export function extractTypesFromProgram(
1415
1479
  checker,
1416
1480
  globalTypeMappings,
1417
1481
  knownTypeNames,
1482
+ typeNode: typeAliasTypeNode,
1418
1483
  });
1419
1484
  const tsdocInfo = extractTsDocInfo(node, checker);
1420
1485
 
@@ -1468,6 +1533,19 @@ export function extractTypesFromProgram(
1468
1533
  return;
1469
1534
  }
1470
1535
 
1536
+ const ignoreFields = detectIgnoreFieldsMetadata({ type, checker });
1537
+
1538
+ if (ignoreFields !== null && kind !== "union") {
1539
+ const allFieldNames = collectAllFieldNames(type, checker);
1540
+ const validationDiagnostics = validateIgnoreFields({
1541
+ typeName: name,
1542
+ ignoreFields,
1543
+ allFieldNames,
1544
+ sourceLocation: typeSourceLocation,
1545
+ });
1546
+ diagnostics.push(...validationDiagnostics);
1547
+ }
1548
+
1471
1549
  const fieldResult =
1472
1550
  kind === "union"
1473
1551
  ? { fields: [], diagnostics: [] }
@@ -1483,6 +1561,7 @@ export function extractTypesFromProgram(
1483
1561
  scalarMappingContext: name.endsWith("Input")
1484
1562
  ? "input"
1485
1563
  : "output",
1564
+ ignoreFields,
1486
1565
  });
1487
1566
  const fields = fieldResult.fields;
1488
1567
  diagnostics.push(...fieldResult.diagnostics);
@@ -1517,11 +1596,9 @@ export function extractTypesFromProgram(
1517
1596
  }
1518
1597
  }
1519
1598
 
1520
- const inlineObjectMembers =
1521
- inlineObjectResult?.hasInlineObjects &&
1522
- !inlineObjectResult.hasNamedTypes
1523
- ? inlineObjectResult.members
1524
- : null;
1599
+ const inlineObjectMembers = inlineObjectResult?.hasInlineObjects
1600
+ ? inlineObjectResult.members
1601
+ : null;
1525
1602
 
1526
1603
  const typeInfo: ExtractedTypeInfo = {
1527
1604
  metadata,