@gqlkit-ts/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/auto-type-generator/auto-type-generator.d.ts +10 -4
  2. package/dist/auto-type-generator/auto-type-generator.d.ts.map +1 -1
  3. package/dist/auto-type-generator/auto-type-generator.js +640 -133
  4. package/dist/auto-type-generator/auto-type-generator.js.map +1 -1
  5. package/dist/auto-type-generator/index.d.ts +8 -1
  6. package/dist/auto-type-generator/index.d.ts.map +1 -1
  7. package/dist/auto-type-generator/index.js +3 -0
  8. package/dist/auto-type-generator/index.js.map +1 -1
  9. package/dist/auto-type-generator/inline-enum-collector.d.ts +13 -5
  10. package/dist/auto-type-generator/inline-enum-collector.d.ts.map +1 -1
  11. package/dist/auto-type-generator/inline-enum-collector.js +107 -71
  12. package/dist/auto-type-generator/inline-enum-collector.js.map +1 -1
  13. package/dist/auto-type-generator/inline-object-traverser.d.ts +20 -0
  14. package/dist/auto-type-generator/inline-object-traverser.d.ts.map +1 -0
  15. package/dist/auto-type-generator/inline-object-traverser.js +22 -0
  16. package/dist/auto-type-generator/inline-object-traverser.js.map +1 -0
  17. package/dist/auto-type-generator/inline-union-collector.d.ts +29 -0
  18. package/dist/auto-type-generator/inline-union-collector.d.ts.map +1 -0
  19. package/dist/auto-type-generator/inline-union-collector.js +216 -0
  20. package/dist/auto-type-generator/inline-union-collector.js.map +1 -0
  21. package/dist/auto-type-generator/inline-union-types.d.ts +29 -0
  22. package/dist/auto-type-generator/inline-union-types.d.ts.map +1 -0
  23. package/dist/auto-type-generator/inline-union-types.js +2 -0
  24. package/dist/auto-type-generator/inline-union-types.js.map +1 -0
  25. package/dist/auto-type-generator/inline-union-validator.d.ts +76 -0
  26. package/dist/auto-type-generator/inline-union-validator.d.ts.map +1 -0
  27. package/dist/auto-type-generator/inline-union-validator.js +329 -0
  28. package/dist/auto-type-generator/inline-union-validator.js.map +1 -0
  29. package/dist/auto-type-generator/naming-convention.d.ts +18 -1
  30. package/dist/auto-type-generator/naming-convention.d.ts.map +1 -1
  31. package/dist/auto-type-generator/naming-convention.js +16 -0
  32. package/dist/auto-type-generator/naming-convention.js.map +1 -1
  33. package/dist/auto-type-generator/resolve-type-generator.d.ts +20 -0
  34. package/dist/auto-type-generator/resolve-type-generator.d.ts.map +1 -0
  35. package/dist/auto-type-generator/resolve-type-generator.js +2 -0
  36. package/dist/auto-type-generator/resolve-type-generator.js.map +1 -0
  37. package/dist/auto-type-generator/resolver-field-iterator.d.ts +13 -0
  38. package/dist/auto-type-generator/resolver-field-iterator.d.ts.map +1 -0
  39. package/dist/auto-type-generator/resolver-field-iterator.js +22 -0
  40. package/dist/auto-type-generator/resolver-field-iterator.js.map +1 -0
  41. package/dist/auto-type-generator/typename-extractor.d.ts +26 -0
  42. package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -0
  43. package/dist/auto-type-generator/typename-extractor.js +142 -0
  44. package/dist/auto-type-generator/typename-extractor.js.map +1 -0
  45. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +35 -0
  46. package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -0
  47. package/dist/auto-type-generator/typename-resolve-type-generator.js +177 -0
  48. package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -0
  49. package/dist/auto-type-generator/typename-types.d.ts +43 -0
  50. package/dist/auto-type-generator/typename-types.d.ts.map +1 -0
  51. package/dist/auto-type-generator/typename-types.js +37 -0
  52. package/dist/auto-type-generator/typename-types.js.map +1 -0
  53. package/dist/auto-type-generator/typename-validator.d.ts +37 -0
  54. package/dist/auto-type-generator/typename-validator.d.ts.map +1 -0
  55. package/dist/auto-type-generator/typename-validator.js +206 -0
  56. package/dist/auto-type-generator/typename-validator.js.map +1 -0
  57. package/dist/cli.js +2 -0
  58. package/dist/cli.js.map +1 -1
  59. package/dist/commands/docs.d.ts +51 -0
  60. package/dist/commands/docs.d.ts.map +1 -0
  61. package/dist/commands/docs.js +154 -0
  62. package/dist/commands/docs.js.map +1 -0
  63. package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
  64. package/dist/gen-orchestrator/orchestrator.js +13 -6
  65. package/dist/gen-orchestrator/orchestrator.js.map +1 -1
  66. package/dist/resolver-extractor/extract-resolvers.d.ts +19 -1
  67. package/dist/resolver-extractor/extract-resolvers.d.ts.map +1 -1
  68. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts +5 -0
  69. package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
  70. package/dist/resolver-extractor/extractor/define-api-extractor.js +14 -61
  71. package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
  72. package/dist/resolver-extractor/index.d.ts +0 -1
  73. package/dist/resolver-extractor/index.d.ts.map +1 -1
  74. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +1 -0
  75. package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts.map +1 -1
  76. package/dist/resolver-extractor/validator/abstract-resolver-validator.js +9 -5
  77. package/dist/resolver-extractor/validator/abstract-resolver-validator.js.map +1 -1
  78. package/dist/schema-generator/emitter/code-emitter.d.ts.map +1 -1
  79. package/dist/schema-generator/emitter/code-emitter.js +20 -0
  80. package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
  81. package/dist/schema-generator/generate-schema.d.ts +1 -0
  82. package/dist/schema-generator/generate-schema.d.ts.map +1 -1
  83. package/dist/schema-generator/generate-schema.js +72 -3
  84. package/dist/schema-generator/generate-schema.js.map +1 -1
  85. package/dist/schema-generator/integrator/result-integrator.d.ts +14 -2
  86. package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
  87. package/dist/schema-generator/integrator/result-integrator.js +54 -1
  88. package/dist/schema-generator/integrator/result-integrator.js.map +1 -1
  89. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts +2 -0
  90. package/dist/schema-generator/resolver-collector/resolver-collector.d.ts.map +1 -1
  91. package/dist/schema-generator/resolver-collector/resolver-collector.js +22 -0
  92. package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
  93. package/dist/shared/enum-prefix-detector.d.ts +63 -0
  94. package/dist/shared/enum-prefix-detector.d.ts.map +1 -0
  95. package/dist/shared/enum-prefix-detector.js +80 -0
  96. package/dist/shared/enum-prefix-detector.js.map +1 -0
  97. package/dist/shared/ignore-fields-detector.d.ts +26 -0
  98. package/dist/shared/ignore-fields-detector.d.ts.map +1 -0
  99. package/dist/shared/ignore-fields-detector.js +83 -0
  100. package/dist/shared/ignore-fields-detector.js.map +1 -0
  101. package/dist/shared/ignore-fields-validator.d.ts +29 -0
  102. package/dist/shared/ignore-fields-validator.d.ts.map +1 -0
  103. package/dist/shared/ignore-fields-validator.js +43 -0
  104. package/dist/shared/ignore-fields-validator.js.map +1 -0
  105. package/dist/shared/index.d.ts +2 -0
  106. package/dist/shared/index.d.ts.map +1 -1
  107. package/dist/shared/index.js.map +1 -1
  108. package/dist/shared/source-location.d.ts +5 -0
  109. package/dist/shared/source-location.d.ts.map +1 -1
  110. package/dist/shared/source-location.js +7 -0
  111. package/dist/shared/source-location.js.map +1 -1
  112. package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
  113. package/dist/type-extractor/converter/graphql-converter.js +21 -7
  114. package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
  115. package/dist/type-extractor/extractor/field-type-resolver.js +42 -3
  116. package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -1
  117. package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
  118. package/dist/type-extractor/extractor/type-extractor.js +88 -23
  119. package/dist/type-extractor/extractor/type-extractor.js.map +1 -1
  120. package/dist/type-extractor/types/diagnostics.d.ts +1 -1
  121. package/dist/type-extractor/types/diagnostics.d.ts.map +1 -1
  122. package/dist/type-extractor/types/ts-type-reference-factory.d.ts +10 -2
  123. package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -1
  124. package/dist/type-extractor/types/ts-type-reference-factory.js +8 -2
  125. package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -1
  126. package/dist/type-extractor/types/typescript.d.ts +4 -0
  127. package/dist/type-extractor/types/typescript.d.ts.map +1 -1
  128. package/docs/coding-agents.md +64 -0
  129. package/docs/configuration.md +6 -20
  130. package/docs/getting-started.md +15 -12
  131. package/docs/index.md +36 -22
  132. package/docs/integration/apollo.md +8 -40
  133. package/docs/integration/drizzle.md +6 -10
  134. package/docs/integration/prisma.md +196 -0
  135. package/docs/integration/yoga.md +8 -40
  136. package/docs/schema/abstract-resolvers.md +117 -0
  137. package/docs/schema/directives.md +5 -0
  138. package/docs/schema/documentation.md +5 -0
  139. package/docs/schema/enums.md +99 -0
  140. package/docs/schema/fields.md +64 -0
  141. package/docs/schema/index.md +21 -0
  142. package/docs/schema/inputs.md +115 -15
  143. package/docs/schema/interfaces.md +31 -1
  144. package/docs/schema/objects.md +40 -0
  145. package/docs/schema/queries-mutations.md +136 -22
  146. package/docs/schema/scalars.md +5 -0
  147. package/docs/schema/unions.md +208 -1
  148. package/docs/what-is-gqlkit.md +13 -8
  149. package/package.json +6 -4
  150. package/src/auto-type-generator/auto-type-generator.ts +946 -201
  151. package/src/auto-type-generator/index.ts +42 -0
  152. package/src/auto-type-generator/inline-enum-collector.ts +187 -139
  153. package/src/auto-type-generator/inline-object-traverser.ts +49 -0
  154. package/src/auto-type-generator/inline-union-collector.ts +402 -0
  155. package/src/auto-type-generator/inline-union-types.ts +33 -0
  156. package/src/auto-type-generator/inline-union-validator.ts +482 -0
  157. package/src/auto-type-generator/naming-convention.ts +38 -1
  158. package/src/auto-type-generator/resolve-type-generator.ts +21 -0
  159. package/src/auto-type-generator/resolver-field-iterator.ts +39 -0
  160. package/src/auto-type-generator/typename-extractor.ts +230 -0
  161. package/src/auto-type-generator/typename-resolve-type-generator.ts +281 -0
  162. package/src/auto-type-generator/typename-types.ts +66 -0
  163. package/src/auto-type-generator/typename-validator.ts +326 -0
  164. package/src/cli.ts +2 -0
  165. package/src/commands/docs.ts +211 -0
  166. package/src/gen-orchestrator/orchestrator.ts +20 -6
  167. package/src/resolver-extractor/extract-resolvers.ts +19 -0
  168. package/src/resolver-extractor/extractor/define-api-extractor.ts +23 -89
  169. package/src/resolver-extractor/index.ts +0 -6
  170. package/src/resolver-extractor/validator/abstract-resolver-validator.ts +16 -8
  171. package/src/schema-generator/emitter/code-emitter.ts +34 -0
  172. package/src/schema-generator/generate-schema.ts +99 -2
  173. package/src/schema-generator/integrator/result-integrator.ts +70 -1
  174. package/src/schema-generator/resolver-collector/resolver-collector.ts +34 -0
  175. package/src/shared/enum-prefix-detector.ts +99 -0
  176. package/src/shared/ignore-fields-detector.ts +109 -0
  177. package/src/shared/ignore-fields-validator.ts +66 -0
  178. package/src/shared/index.ts +2 -0
  179. package/src/shared/source-location.ts +11 -0
  180. package/src/type-extractor/converter/graphql-converter.ts +31 -7
  181. package/src/type-extractor/extractor/field-type-resolver.ts +48 -3
  182. package/src/type-extractor/extractor/type-extractor.ts +103 -26
  183. package/src/type-extractor/types/diagnostics.ts +12 -2
  184. package/src/type-extractor/types/ts-type-reference-factory.ts +18 -5
  185. package/src/type-extractor/types/typescript.ts +4 -0
  186. package/dist/resolver-extractor/validator/only-validator.d.ts +0 -61
  187. package/dist/resolver-extractor/validator/only-validator.d.ts.map +0 -1
  188. package/dist/resolver-extractor/validator/only-validator.js +0 -76
  189. package/dist/resolver-extractor/validator/only-validator.js.map +0 -1
  190. package/src/resolver-extractor/validator/only-validator.ts +0 -158
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Converts an enum name from any naming convention to UPPER_SNAKE_CASE.
3
+ *
4
+ * Supports PascalCase, camelCase, UPPER_SNAKE_CASE, and single word inputs.
5
+ *
6
+ * @example
7
+ * toUpperSnakeCase("UserStatus") // => "USER_STATUS"
8
+ * toUpperSnakeCase("userStatus") // => "USER_STATUS"
9
+ * toUpperSnakeCase("USER_STATUS") // => "USER_STATUS"
10
+ * toUpperSnakeCase("Status") // => "STATUS"
11
+ */
12
+ export function toUpperSnakeCase(name: string): string {
13
+ return name
14
+ .replace(/([a-z])([A-Z])/g, "$1_$2")
15
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
16
+ .toUpperCase();
17
+ }
18
+
19
+ /**
20
+ * Builds the prefix candidate string for enum prefix stripping.
21
+ *
22
+ * Converts the enum name to UPPER_SNAKE_CASE and appends "_" to create
23
+ * the prefix that will be checked against all enum values.
24
+ *
25
+ * @example
26
+ * buildEnumPrefixCandidate("UserStatus") // => "USER_STATUS_"
27
+ * buildEnumPrefixCandidate("userStatus") // => "USER_STATUS_"
28
+ * buildEnumPrefixCandidate("USER_STATUS") // => "USER_STATUS_"
29
+ * buildEnumPrefixCandidate("Status") // => "STATUS_"
30
+ */
31
+ export function buildEnumPrefixCandidate(enumName: string): string {
32
+ return `${toUpperSnakeCase(enumName)}_`;
33
+ }
34
+
35
+ export interface DetectEnumPrefixParams {
36
+ readonly enumName: string;
37
+ readonly memberValues: ReadonlyArray<string>;
38
+ }
39
+
40
+ export interface DetectEnumPrefixResult {
41
+ readonly shouldStrip: boolean;
42
+ readonly prefix: string | null;
43
+ }
44
+
45
+ /**
46
+ * Detects whether an enum is a candidate for prefix stripping.
47
+ *
48
+ * Conditions for stripping:
49
+ * 1. All memberValues must start with `${toUpperSnakeCase(enumName)}_`
50
+ * 2. After removing the prefix, the remaining string must be non-empty
51
+ *
52
+ * @example
53
+ * detectEnumPrefix({
54
+ * enumName: "UserStatus",
55
+ * memberValues: ["USER_STATUS_ACTIVE", "USER_STATUS_INACTIVE"]
56
+ * })
57
+ * // => { shouldStrip: true, prefix: "USER_STATUS_" }
58
+ *
59
+ * detectEnumPrefix({
60
+ * enumName: "UserStatus",
61
+ * memberValues: ["ACTIVE", "INACTIVE"]
62
+ * })
63
+ * // => { shouldStrip: false, prefix: null }
64
+ */
65
+ export function detectEnumPrefix(
66
+ params: DetectEnumPrefixParams,
67
+ ): DetectEnumPrefixResult {
68
+ const { enumName, memberValues } = params;
69
+
70
+ if (memberValues.length === 0) {
71
+ return { shouldStrip: false, prefix: null };
72
+ }
73
+
74
+ const prefixCandidate = buildEnumPrefixCandidate(enumName);
75
+
76
+ for (const value of memberValues) {
77
+ if (!value.startsWith(prefixCandidate)) {
78
+ return { shouldStrip: false, prefix: null };
79
+ }
80
+
81
+ const stripped = value.slice(prefixCandidate.length);
82
+ if (stripped === "") {
83
+ return { shouldStrip: false, prefix: null };
84
+ }
85
+ }
86
+
87
+ return { shouldStrip: true, prefix: prefixCandidate };
88
+ }
89
+
90
+ /**
91
+ * Removes the detected prefix from an enum value.
92
+ *
93
+ * @example
94
+ * stripEnumPrefix("USER_STATUS_ACTIVE", "USER_STATUS_")
95
+ * // => "ACTIVE"
96
+ */
97
+ export function stripEnumPrefix(value: string, prefix: string): string {
98
+ return value.slice(prefix.length);
99
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * IgnoreFields metadata detector.
3
+ *
4
+ * This module provides functions to detect ignoreFields metadata embedded
5
+ * in TypeScript intersection types using the $gqlkitTypeMeta property.
6
+ */
7
+
8
+ import ts from "typescript";
9
+ import { METADATA_PROPERTIES } from "./constants.js";
10
+ import { getActualMetadataType } from "./metadata-detector.js";
11
+
12
+ const TYPE_META_PROPERTY = METADATA_PROPERTIES.TYPE_META;
13
+
14
+ /**
15
+ * Parameters for detectIgnoreFieldsMetadata function.
16
+ */
17
+ export interface DetectIgnoreFieldsParams {
18
+ readonly type: ts.Type;
19
+ readonly checker: ts.TypeChecker;
20
+ }
21
+
22
+ /**
23
+ * Extracts ignoreFields from a type's metadata.
24
+ * Returns null if the type doesn't have $gqlkitTypeMeta or ignoreFields is not specified.
25
+ */
26
+ function extractIgnoreFieldsFromType(
27
+ type: ts.Type,
28
+ checker: ts.TypeChecker,
29
+ ): ReadonlySet<string> | null {
30
+ const metaProp = type.getProperty(TYPE_META_PROPERTY);
31
+ if (!metaProp) {
32
+ return null;
33
+ }
34
+
35
+ const rawMetadataType = checker.getTypeOfSymbol(metaProp);
36
+ const metadataType = getActualMetadataType(rawMetadataType);
37
+ if (!metadataType) {
38
+ return null;
39
+ }
40
+
41
+ const ignoreFieldsProp = metadataType.getProperty("ignoreFields");
42
+ if (!ignoreFieldsProp) {
43
+ return null;
44
+ }
45
+
46
+ const rawIgnoreFieldsType = checker.getTypeOfSymbol(ignoreFieldsProp);
47
+ const ignoreFieldsType = getActualMetadataType(rawIgnoreFieldsType);
48
+ if (!ignoreFieldsType) {
49
+ return null;
50
+ }
51
+
52
+ return extractStringLiteralUnion(ignoreFieldsType);
53
+ }
54
+
55
+ /**
56
+ * Extracts string literals from a type (single literal or union of literals).
57
+ */
58
+ function extractStringLiteralUnion(type: ts.Type): ReadonlySet<string> | null {
59
+ if (type.flags & ts.TypeFlags.StringLiteral) {
60
+ const value = (type as ts.StringLiteralType).value;
61
+ return new Set([value]);
62
+ }
63
+
64
+ if (type.isUnion()) {
65
+ const values = new Set<string>();
66
+ for (const member of type.types) {
67
+ if (member.flags & ts.TypeFlags.StringLiteral) {
68
+ values.add((member as ts.StringLiteralType).value);
69
+ }
70
+ }
71
+ if (values.size > 0) {
72
+ return values;
73
+ }
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * Detects ignoreFields metadata from a TypeScript type.
81
+ *
82
+ * This function analyzes TypeScript types to detect ignoreFields metadata
83
+ * from the $gqlkitTypeMeta property. It extracts string literal or string
84
+ * literal union types and returns them as a Set.
85
+ *
86
+ * @param params - The detection parameters containing type and checker
87
+ * @returns The ignoreFields set or null if not specified
88
+ */
89
+ export function detectIgnoreFieldsMetadata(
90
+ params: DetectIgnoreFieldsParams,
91
+ ): ReadonlySet<string> | null {
92
+ const { type, checker } = params;
93
+
94
+ const result = extractIgnoreFieldsFromType(type, checker);
95
+ if (result) {
96
+ return result;
97
+ }
98
+
99
+ if (type.isIntersection()) {
100
+ for (const member of type.types) {
101
+ const memberResult = extractIgnoreFieldsFromType(member, checker);
102
+ if (memberResult) {
103
+ return memberResult;
104
+ }
105
+ }
106
+ }
107
+
108
+ return null;
109
+ }
@@ -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
+ }
@@ -3,6 +3,10 @@ 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";
6
10
  import { convertTsTypeToGraphQLType } from "../../shared/type-converter.js";
7
11
  import type {
8
12
  Diagnostic,
@@ -60,6 +64,11 @@ interface ConvertEnumMembersResult {
60
64
  readonly hasError: boolean;
61
65
  }
62
66
 
67
+ interface ConvertedMemberInfo {
68
+ readonly convertedName: string;
69
+ readonly member: EnumMemberInfo;
70
+ }
71
+
63
72
  function convertEnumMembers(
64
73
  params: ConvertEnumMembersParams,
65
74
  ): ConvertEnumMembersResult {
@@ -69,7 +78,7 @@ function convertEnumMembers(
69
78
  let isNumeric = false;
70
79
  let needsMapping = false;
71
80
 
72
- const convertedNameToOriginals = new Map<string, string[]>();
81
+ const eligibleMembers: ConvertedMemberInfo[] = [];
73
82
 
74
83
  for (const member of members) {
75
84
  const convertedName = toScreamingSnakeCase(member.name);
@@ -93,16 +102,31 @@ function convertEnumMembers(
93
102
  isNumeric = true;
94
103
  }
95
104
 
96
- if (convertedName !== member.value) {
105
+ eligibleMembers.push({ convertedName, member });
106
+ }
107
+
108
+ const prefixDetectionResult = detectEnumPrefix({
109
+ enumName,
110
+ memberValues: eligibleMembers.map((m) => m.convertedName),
111
+ });
112
+
113
+ const finalNameToOriginals = new Map<string, string[]>();
114
+
115
+ for (const { convertedName, member } of eligibleMembers) {
116
+ let finalName = convertedName;
117
+ if (prefixDetectionResult.shouldStrip && prefixDetectionResult.prefix) {
118
+ finalName = stripEnumPrefix(convertedName, prefixDetectionResult.prefix);
119
+ needsMapping = true;
120
+ } else if (convertedName !== member.value) {
97
121
  needsMapping = true;
98
122
  }
99
123
 
100
- const existingOriginals = convertedNameToOriginals.get(convertedName) ?? [];
124
+ const existingOriginals = finalNameToOriginals.get(finalName) ?? [];
101
125
  existingOriginals.push(member.value);
102
- convertedNameToOriginals.set(convertedName, existingOriginals);
126
+ finalNameToOriginals.set(finalName, existingOriginals);
103
127
 
104
128
  values.push({
105
- name: convertedName,
129
+ name: finalName,
106
130
  originalValue: member.value,
107
131
  numericValue: member.numericValue,
108
132
  description: member.description,
@@ -111,11 +135,11 @@ function convertEnumMembers(
111
135
  }
112
136
 
113
137
  let hasError = false;
114
- for (const [convertedName, originals] of convertedNameToOriginals) {
138
+ for (const [finalName, originals] of finalNameToOriginals) {
115
139
  if (originals.length > 1) {
116
140
  diagnostics.push({
117
141
  code: "DUPLICATE_ENUM_VALUE_AFTER_CONVERSION",
118
- message: `Enum '${enumName}' has duplicate value '${convertedName}' after conversion (from '${originals.join("' and '")}')`,
142
+ message: `Enum '${enumName}' has duplicate value '${finalName}' after conversion (from '${originals.join("' and '")}')`,
119
143
  severity: "error",
120
144
  location: enumLocation,
121
145
  });
@@ -287,6 +287,28 @@ function resolveFieldTypeInternal(
287
287
  }
288
288
  }
289
289
 
290
+ // Type alias expansion: type aliases not in knownTypeNames should be expanded as inline objects
291
+ // This handles cases like: type MyPayload = { user: User; success: boolean; }
292
+ // where MyPayload is used as return type but not declared as a schema type
293
+ // Only expand if the underlying type is an anonymous object literal, not a named type
294
+ // IMPORTANT: Only expand if the name doesn't exist in schema at all.
295
+ // If the name exists but symbols don't match, that's a shadowing case handled by later logic.
296
+ if (type.aliasSymbol) {
297
+ const aliasName = type.aliasSymbol.getName();
298
+ if (!knownTypeNames.has(aliasName)) {
299
+ // Check if this is an anonymous object type (not an interface or another named type)
300
+ // using ts.ObjectFlags.Anonymous for a more robust check than internal symbol names
301
+ const isAnonymousObject =
302
+ (type.flags & ts.TypeFlags.Object) !== 0 &&
303
+ ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) !== 0;
304
+
305
+ if (isAnonymousObject) {
306
+ // Not a known schema type and is an anonymous object - expand to generate Payload type
307
+ return tryExtractAsInlineObject(type, ctx);
308
+ }
309
+ }
310
+ }
311
+
290
312
  // Extract type name from typeNode first (takes precedence over type.symbol).
291
313
  // This handles cases like:
292
314
  // - `typeof def` where the type's symbol is internal (__type, __object)
@@ -406,7 +428,7 @@ function tryExtractAsInlineObject(
406
428
  type: ts.Type,
407
429
  ctx: InternalFieldTypeContext,
408
430
  ): TSTypeReference {
409
- const { visitedTypes } = ctx;
431
+ const { visitedTypes, checker } = ctx;
410
432
  if (visitedTypes.has(type)) {
411
433
  // Cycle detected, return a placeholder reference
412
434
  const typeName = type.symbol?.getName() ?? "Unknown";
@@ -417,11 +439,34 @@ function tryExtractAsInlineObject(
417
439
 
418
440
  const inlineProperties = extractInlineObjectPropertiesShared(
419
441
  type,
420
- ctx.checker,
442
+ checker,
421
443
  (propType) => resolveFieldTypeInternal(propType, undefined, ctx),
422
444
  );
423
445
 
424
- return createInlineObjectType(inlineProperties);
446
+ // Extract type-level TSDoc from the alias symbol if present (Requirement 7.2)
447
+ // Only extract from user-defined types, not built-in TypeScript utility types
448
+ let description: string | null = null;
449
+ let deprecated: DeprecationInfo | null = null;
450
+ if (type.aliasSymbol) {
451
+ const declarations = type.aliasSymbol.getDeclarations();
452
+ const isUserDefined =
453
+ declarations?.some((decl) => {
454
+ const sourceFile = decl.getSourceFile();
455
+ return !sourceFile.isDeclarationFile;
456
+ }) ?? false;
457
+
458
+ if (isUserDefined) {
459
+ const tsdocInfo = extractTsDocFromSymbol(type.aliasSymbol, checker);
460
+ description = tsdocInfo.description;
461
+ deprecated = tsdocInfo.deprecated;
462
+ }
463
+ }
464
+
465
+ return createInlineObjectType({
466
+ properties: inlineProperties,
467
+ description,
468
+ deprecated,
469
+ });
425
470
  }
426
471
 
427
472
  /**
@@ -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,