@gqlkit-ts/cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto-type-generator/auto-type-generator.d.ts +7 -0
- package/dist/auto-type-generator/auto-type-generator.d.ts.map +1 -1
- package/dist/auto-type-generator/auto-type-generator.js +375 -55
- package/dist/auto-type-generator/auto-type-generator.js.map +1 -1
- package/dist/auto-type-generator/discriminator-field-validator.d.ts +26 -0
- package/dist/auto-type-generator/discriminator-field-validator.d.ts.map +1 -0
- package/dist/auto-type-generator/discriminator-field-validator.js +242 -0
- package/dist/auto-type-generator/discriminator-field-validator.js.map +1 -0
- package/dist/auto-type-generator/discriminator-naming.d.ts +11 -0
- package/dist/auto-type-generator/discriminator-naming.d.ts.map +1 -0
- package/dist/auto-type-generator/discriminator-naming.js +15 -0
- package/dist/auto-type-generator/discriminator-naming.js.map +1 -0
- package/dist/auto-type-generator/discriminator-resolve-type-generator.d.ts +44 -0
- package/dist/auto-type-generator/discriminator-resolve-type-generator.d.ts.map +1 -0
- package/dist/auto-type-generator/discriminator-resolve-type-generator.js +77 -0
- package/dist/auto-type-generator/discriminator-resolve-type-generator.js.map +1 -0
- package/dist/auto-type-generator/index.d.ts +3 -0
- package/dist/auto-type-generator/index.d.ts.map +1 -1
- package/dist/auto-type-generator/index.js +3 -0
- package/dist/auto-type-generator/index.js.map +1 -1
- package/dist/auto-type-generator/inline-enum-collector.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-enum-collector.js +14 -7
- package/dist/auto-type-generator/inline-enum-collector.js.map +1 -1
- package/dist/auto-type-generator/inline-object-converter.d.ts +12 -0
- package/dist/auto-type-generator/inline-object-converter.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-object-converter.js +72 -0
- package/dist/auto-type-generator/inline-object-converter.js.map +1 -0
- package/dist/auto-type-generator/inline-object-traverser.d.ts +2 -1
- package/dist/auto-type-generator/inline-object-traverser.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-object-traverser.js +22 -4
- package/dist/auto-type-generator/inline-object-traverser.js.map +1 -1
- package/dist/auto-type-generator/inline-union-collector.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-union-collector.js +20 -6
- package/dist/auto-type-generator/inline-union-collector.js.map +1 -1
- package/dist/auto-type-generator/inline-union-types.d.ts +2 -0
- package/dist/auto-type-generator/inline-union-types.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-union-validator.js +3 -3
- package/dist/auto-type-generator/inline-union-validator.js.map +1 -1
- package/dist/auto-type-generator/intersection-flattener.d.ts +44 -0
- package/dist/auto-type-generator/intersection-flattener.d.ts.map +1 -0
- package/dist/auto-type-generator/intersection-flattener.js +398 -0
- package/dist/auto-type-generator/intersection-flattener.js.map +1 -0
- package/dist/auto-type-generator/naming-convention.d.ts +21 -0
- package/dist/auto-type-generator/naming-convention.d.ts.map +1 -1
- package/dist/auto-type-generator/naming-convention.js +145 -1
- package/dist/auto-type-generator/naming-convention.js.map +1 -1
- package/dist/auto-type-generator/typename-extractor.d.ts +2 -0
- package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -1
- package/dist/auto-type-generator/typename-extractor.js +11 -3
- package/dist/auto-type-generator/typename-extractor.js.map +1 -1
- package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +2 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -1
- package/dist/auto-type-generator/typename-resolve-type-generator.js +12 -84
- package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -1
- package/dist/auto-type-generator/typename-types.d.ts +4 -0
- package/dist/auto-type-generator/typename-types.d.ts.map +1 -1
- package/dist/auto-type-generator/typename-types.js +6 -0
- package/dist/auto-type-generator/typename-types.js.map +1 -1
- package/dist/auto-type-generator/typename-validator.d.ts.map +1 -1
- package/dist/auto-type-generator/typename-validator.js +4 -3
- package/dist/auto-type-generator/typename-validator.js.map +1 -1
- package/dist/commands/gen.d.ts.map +1 -1
- package/dist/commands/gen.js +2 -1
- package/dist/commands/gen.js.map +1 -1
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config-loader/index.d.ts +1 -1
- package/dist/config-loader/index.d.ts.map +1 -1
- package/dist/config-loader/index.js.map +1 -1
- package/dist/config-loader/loader.d.ts +6 -0
- package/dist/config-loader/loader.d.ts.map +1 -1
- package/dist/config-loader/loader.js +1 -0
- package/dist/config-loader/loader.js.map +1 -1
- package/dist/config-loader/validator.d.ts.map +1 -1
- package/dist/config-loader/validator.js +84 -1
- package/dist/config-loader/validator.js.map +1 -1
- package/dist/gen-orchestrator/orchestrator.d.ts +2 -1
- package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/gen-orchestrator/orchestrator.js +15 -2
- package/dist/gen-orchestrator/orchestrator.js.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.js +4 -0
- package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
- package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +2 -0
- package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts.map +1 -1
- package/dist/resolver-extractor/validator/abstract-resolver-validator.js +16 -3
- package/dist/resolver-extractor/validator/abstract-resolver-validator.js.map +1 -1
- package/dist/schema-generator/emitter/code-emitter.d.ts.map +1 -1
- package/dist/schema-generator/emitter/code-emitter.js +13 -1
- package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
- package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.d.ts +18 -0
- package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.d.ts.map +1 -0
- package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.js +89 -0
- package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.js.map +1 -0
- package/dist/schema-generator/generate-schema.d.ts +2 -0
- package/dist/schema-generator/generate-schema.d.ts.map +1 -1
- package/dist/schema-generator/generate-schema.js +69 -10
- package/dist/schema-generator/generate-schema.js.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.d.ts +4 -0
- package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.js +18 -2
- package/dist/schema-generator/integrator/result-integrator.js.map +1 -1
- package/dist/schema-generator/resolver-collector/resolver-collector.d.ts +2 -0
- package/dist/schema-generator/resolver-collector/resolver-collector.d.ts.map +1 -1
- package/dist/schema-generator/resolver-collector/resolver-collector.js +4 -0
- package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +14 -1
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/enum-prefix-detector.d.ts.map +1 -1
- package/dist/shared/enum-prefix-detector.js +78 -8
- package/dist/shared/enum-prefix-detector.js.map +1 -1
- package/dist/shared/inline-object-utils.js +1 -1
- package/dist/shared/inline-object-utils.js.map +1 -1
- package/dist/shared/type-converter.d.ts.map +1 -1
- package/dist/shared/type-converter.js +55 -0
- package/dist/shared/type-converter.js.map +1 -1
- package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
- package/dist/type-extractor/converter/graphql-converter.js +11 -1
- package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
- package/dist/type-extractor/extractor/field-type-resolver.d.ts +18 -0
- package/dist/type-extractor/extractor/field-type-resolver.d.ts.map +1 -1
- package/dist/type-extractor/extractor/field-type-resolver.js +198 -15
- package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -1
- package/dist/type-extractor/extractor/type-extractor.d.ts +1 -0
- package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor/extractor/type-extractor.js +100 -9
- package/dist/type-extractor/extractor/type-extractor.js.map +1 -1
- package/dist/type-extractor/types/diagnostics.d.ts +1 -1
- package/dist/type-extractor/types/diagnostics.d.ts.map +1 -1
- package/dist/type-extractor/types/index.d.ts +1 -1
- package/dist/type-extractor/types/index.d.ts.map +1 -1
- package/dist/type-extractor/types/index.js +1 -1
- package/dist/type-extractor/types/index.js.map +1 -1
- package/dist/type-extractor/types/ts-type-reference-factory.d.ts +7 -1
- package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -1
- package/dist/type-extractor/types/ts-type-reference-factory.js +18 -3
- package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -1
- package/dist/type-extractor/types/typescript.d.ts +3 -1
- package/dist/type-extractor/types/typescript.d.ts.map +1 -1
- package/dist/type-extractor/validator/type-validator.d.ts.map +1 -1
- package/dist/type-extractor/validator/type-validator.js +6 -1
- package/dist/type-extractor/validator/type-validator.js.map +1 -1
- package/docs/configuration.md +19 -0
- package/docs/index.md +1 -0
- package/docs/integration/ai-sdk.md +189 -0
- package/docs/schema/unions.md +117 -0
- package/package.json +2 -2
- package/src/auto-type-generator/auto-type-generator.ts +576 -58
- package/src/auto-type-generator/discriminator-field-validator.ts +368 -0
- package/src/auto-type-generator/discriminator-naming.ts +24 -0
- package/src/auto-type-generator/discriminator-resolve-type-generator.ts +136 -0
- package/src/auto-type-generator/index.ts +17 -0
- package/src/auto-type-generator/inline-enum-collector.ts +19 -4
- package/src/auto-type-generator/inline-object-converter.ts +100 -0
- package/src/auto-type-generator/inline-object-traverser.ts +33 -7
- package/src/auto-type-generator/inline-union-collector.ts +26 -4
- package/src/auto-type-generator/inline-union-types.ts +2 -0
- package/src/auto-type-generator/inline-union-validator.ts +3 -3
- package/src/auto-type-generator/intersection-flattener.ts +554 -0
- package/src/auto-type-generator/naming-convention.ts +205 -1
- package/src/auto-type-generator/typename-extractor.ts +17 -3
- package/src/auto-type-generator/typename-resolve-type-generator.ts +19 -108
- package/src/auto-type-generator/typename-types.ts +7 -0
- package/src/auto-type-generator/typename-validator.ts +4 -3
- package/src/commands/gen.ts +9 -2
- package/src/config/types.ts +10 -0
- package/src/config-loader/index.ts +1 -0
- package/src/config-loader/loader.ts +11 -0
- package/src/config-loader/validator.ts +100 -1
- package/src/gen-orchestrator/orchestrator.ts +19 -2
- package/src/resolver-extractor/extractor/define-api-extractor.ts +4 -0
- package/src/resolver-extractor/validator/abstract-resolver-validator.ts +20 -6
- package/src/schema-generator/emitter/code-emitter.ts +26 -1
- package/src/schema-generator/emitter/discriminator-resolve-type-emitter.ts +125 -0
- package/src/schema-generator/generate-schema.ts +100 -13
- package/src/schema-generator/integrator/result-integrator.ts +25 -1
- package/src/schema-generator/resolver-collector/resolver-collector.ts +7 -0
- package/src/shared/constants.ts +15 -1
- package/src/shared/enum-prefix-detector.ts +96 -8
- package/src/shared/inline-object-utils.ts +1 -1
- package/src/shared/type-converter.ts +63 -0
- package/src/type-extractor/converter/graphql-converter.ts +17 -1
- package/src/type-extractor/extractor/field-type-resolver.ts +241 -16
- package/src/type-extractor/extractor/type-extractor.ts +119 -5
- package/src/type-extractor/types/diagnostics.ts +10 -1
- package/src/type-extractor/types/index.ts +2 -1
- package/src/type-extractor/types/ts-type-reference-factory.ts +24 -3
- package/src/type-extractor/types/typescript.ts +6 -2
- package/src/type-extractor/validator/type-validator.ts +6 -1
|
@@ -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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
163
|
+
const stripped = value.slice(prefixCandidate.length);
|
|
164
|
+
if (stripped === "") {
|
|
165
|
+
matches = false;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
79
168
|
}
|
|
80
169
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return { shouldStrip: false, prefix: null };
|
|
170
|
+
if (matches) {
|
|
171
|
+
return { shouldStrip: true, prefix: prefixCandidate };
|
|
84
172
|
}
|
|
85
173
|
}
|
|
86
174
|
|
|
87
|
-
return { shouldStrip:
|
|
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)) {
|
|
@@ -4,6 +4,21 @@ import type {
|
|
|
4
4
|
} from "../type-extractor/types/index.js";
|
|
5
5
|
import { PRIMITIVE_TYPE_MAP } from "./constants.js";
|
|
6
6
|
|
|
7
|
+
const GRAPHQL_INT_MIN = -(2 ** 31);
|
|
8
|
+
const GRAPHQL_INT_MAX = 2 ** 31 - 1;
|
|
9
|
+
|
|
10
|
+
function numericLiteralToGraphQLScalar(name: string | null): string {
|
|
11
|
+
const num = Number(name);
|
|
12
|
+
if (
|
|
13
|
+
Number.isInteger(num) &&
|
|
14
|
+
num >= GRAPHQL_INT_MIN &&
|
|
15
|
+
num <= GRAPHQL_INT_MAX
|
|
16
|
+
) {
|
|
17
|
+
return "Int";
|
|
18
|
+
}
|
|
19
|
+
return "Float";
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
function convertElementTypeName(elementType: TSTypeReference): string {
|
|
8
23
|
if (elementType.kind === "scalar") {
|
|
9
24
|
return elementType.scalarInfo?.scalarName ?? elementType.name ?? "String";
|
|
@@ -20,6 +35,18 @@ function convertElementTypeName(elementType: TSTypeReference): string {
|
|
|
20
35
|
if (elementType.kind === "inlineEnum") {
|
|
21
36
|
return "__INLINE_ENUM__";
|
|
22
37
|
}
|
|
38
|
+
if (elementType.kind === "union") {
|
|
39
|
+
return "__INLINE_UNION__";
|
|
40
|
+
}
|
|
41
|
+
if (elementType.kind === "stringLiteral") {
|
|
42
|
+
return "String";
|
|
43
|
+
}
|
|
44
|
+
if (elementType.kind === "numericLiteral") {
|
|
45
|
+
return numericLiteralToGraphQLScalar(elementType.name);
|
|
46
|
+
}
|
|
47
|
+
if (elementType.kind === "never") {
|
|
48
|
+
return "__NEVER__";
|
|
49
|
+
}
|
|
23
50
|
return elementType.name ?? "String";
|
|
24
51
|
}
|
|
25
52
|
|
|
@@ -90,6 +117,42 @@ export function convertTsTypeToGraphQLType(
|
|
|
90
117
|
};
|
|
91
118
|
}
|
|
92
119
|
|
|
120
|
+
if (tsType.kind === "union") {
|
|
121
|
+
return {
|
|
122
|
+
typeName: "__INLINE_UNION__",
|
|
123
|
+
nullable,
|
|
124
|
+
list: false,
|
|
125
|
+
listItemNullable: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (tsType.kind === "stringLiteral") {
|
|
130
|
+
return {
|
|
131
|
+
typeName: "String",
|
|
132
|
+
nullable,
|
|
133
|
+
list: false,
|
|
134
|
+
listItemNullable: null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (tsType.kind === "numericLiteral") {
|
|
139
|
+
return {
|
|
140
|
+
typeName: numericLiteralToGraphQLScalar(tsType.name),
|
|
141
|
+
nullable,
|
|
142
|
+
list: false,
|
|
143
|
+
listItemNullable: null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (tsType.kind === "never") {
|
|
148
|
+
return {
|
|
149
|
+
typeName: "__NEVER__",
|
|
150
|
+
nullable,
|
|
151
|
+
list: false,
|
|
152
|
+
listItemNullable: null,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
93
156
|
return {
|
|
94
157
|
typeName: tsType.name ?? "String",
|
|
95
158
|
nullable,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTypenameFieldName } from "../../auto-type-generator/typename-types.js";
|
|
1
2
|
import {
|
|
2
3
|
BUILT_IN_SCALARS,
|
|
3
4
|
isBuiltInScalar,
|
|
@@ -152,6 +153,11 @@ function convertFields(
|
|
|
152
153
|
const diagnostics: Diagnostic[] = [];
|
|
153
154
|
|
|
154
155
|
for (const field of extracted.fields) {
|
|
156
|
+
// Typename discrimination fields are silently excluded from the schema
|
|
157
|
+
if (isTypenameFieldName(field.name)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
155
161
|
const eligibility = isEligibleField({
|
|
156
162
|
fieldName: field.name,
|
|
157
163
|
kind: isInput ? "input" : "object",
|
|
@@ -171,9 +177,19 @@ function convertFields(
|
|
|
171
177
|
continue;
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
const graphqlType = convertTsTypeToGraphQLType(
|
|
181
|
+
field.tsType,
|
|
182
|
+
field.optional,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Skip fields with never type — they represent impossible values
|
|
186
|
+
if (graphqlType.typeName === "__NEVER__") {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
174
190
|
fields.push({
|
|
175
191
|
name: field.name,
|
|
176
|
-
type:
|
|
192
|
+
type: graphqlType,
|
|
177
193
|
description: field.description,
|
|
178
194
|
deprecated: field.deprecated,
|
|
179
195
|
directives: field.directives,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
1
2
|
import ts from "typescript";
|
|
2
3
|
import {
|
|
3
4
|
detectBrandedType,
|
|
@@ -7,6 +8,10 @@ import { isInternalTypeSymbol } from "../../shared/constants.js";
|
|
|
7
8
|
import { extractInlineObjectProperties as extractInlineObjectPropertiesShared } from "../../shared/inline-object-extractor.js";
|
|
8
9
|
import { isInlineObjectType } from "../../shared/inline-object-utils.js";
|
|
9
10
|
import { detectScalarMetadata } from "../../shared/metadata-detector.js";
|
|
11
|
+
import {
|
|
12
|
+
getSourceLocationFromNode,
|
|
13
|
+
type SourceLocation,
|
|
14
|
+
} from "../../shared/source-location.js";
|
|
10
15
|
import {
|
|
11
16
|
type DeprecationInfo,
|
|
12
17
|
extractTsDocFromSymbol,
|
|
@@ -25,14 +30,17 @@ import type {
|
|
|
25
30
|
ScalarMappingContext,
|
|
26
31
|
} from "../mapper/scalar-base-type-mapper.js";
|
|
27
32
|
import { lookupScalarMapping } from "../mapper/scalar-base-type-mapper.js";
|
|
33
|
+
import type { DiagnosticCode } from "../types/diagnostics.js";
|
|
28
34
|
import {
|
|
29
35
|
createArrayType,
|
|
30
36
|
createInlineEnumType,
|
|
31
37
|
createInlineObjectType,
|
|
32
|
-
|
|
38
|
+
createNeverType,
|
|
39
|
+
createNumericLiteralType,
|
|
33
40
|
createPrimitiveType,
|
|
34
41
|
createReferenceType,
|
|
35
42
|
createScalarType,
|
|
43
|
+
createStringLiteralType,
|
|
36
44
|
createUnionType,
|
|
37
45
|
} from "../types/ts-type-reference-factory.js";
|
|
38
46
|
import type {
|
|
@@ -41,6 +49,20 @@ import type {
|
|
|
41
49
|
} from "../types/typescript.js";
|
|
42
50
|
import type { GlobalTypeMapping } from "./type-extractor.js";
|
|
43
51
|
|
|
52
|
+
export interface DiscoveredTypeEntry {
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly tsType: ts.Type;
|
|
55
|
+
readonly tsSymbol: ts.Symbol;
|
|
56
|
+
readonly sourceFile: string;
|
|
57
|
+
readonly sourceLocation: SourceLocation;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface FieldTypeResolverDiagnostic {
|
|
61
|
+
readonly code: DiagnosticCode;
|
|
62
|
+
readonly message: string;
|
|
63
|
+
readonly severity: "error" | "warning";
|
|
64
|
+
}
|
|
65
|
+
|
|
44
66
|
export interface FieldTypeResolverContext {
|
|
45
67
|
readonly checker: ts.TypeChecker;
|
|
46
68
|
readonly knownTypeNames: ReadonlySet<string>;
|
|
@@ -52,6 +74,10 @@ export interface FieldTypeResolverContext {
|
|
|
52
74
|
readonly scalarMappingTable: ScalarBaseTypeMappingTable | null;
|
|
53
75
|
/** Current resolution context for scalar mapping (input or output) */
|
|
54
76
|
readonly scalarMappingContext: ScalarMappingContext;
|
|
77
|
+
/** Mutable map for collecting transitively discovered types */
|
|
78
|
+
readonly discoveredTypes: Map<string, DiscoveredTypeEntry> | null;
|
|
79
|
+
/** Mutable array for collecting diagnostics during field type resolution */
|
|
80
|
+
readonly diagnostics: FieldTypeResolverDiagnostic[];
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
/**
|
|
@@ -207,11 +233,28 @@ function resolveFieldTypeInternal(
|
|
|
207
233
|
return { ...innerResult, nullable };
|
|
208
234
|
}
|
|
209
235
|
|
|
210
|
-
const memberResults = nonNullTypes.map((t) =>
|
|
211
|
-
resolveFieldTypeInternal(t, undefined, ctx)
|
|
212
|
-
|
|
236
|
+
const memberResults = nonNullTypes.map((t) => {
|
|
237
|
+
const result = resolveFieldTypeInternal(t, undefined, ctx);
|
|
238
|
+
// If the result is an unresolvable reference and the original type is an
|
|
239
|
+
// object type with properties, try to expand it as an inline object.
|
|
240
|
+
// This handles external library types used as union members.
|
|
241
|
+
// When type discovery is active, discovered types are kept as references
|
|
242
|
+
// so they can be registered with their original names later.
|
|
243
|
+
if (
|
|
244
|
+
result.kind === "reference" &&
|
|
245
|
+
result.name !== null &&
|
|
246
|
+
!ctx.knownTypeNames.has(result.name) &&
|
|
247
|
+
t.flags & ts.TypeFlags.Object &&
|
|
248
|
+
t.getProperties().length > 0 &&
|
|
249
|
+
!ctx.discoveredTypes?.has(result.name)
|
|
250
|
+
) {
|
|
251
|
+
return tryExtractAsInlineObject(t, ctx, result.name);
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
});
|
|
213
255
|
|
|
214
|
-
|
|
256
|
+
const aliasName = type.aliasSymbol?.getName() ?? null;
|
|
257
|
+
return createUnionType({ members: memberResults, nullable, aliasName });
|
|
215
258
|
}
|
|
216
259
|
|
|
217
260
|
// Array type handling
|
|
@@ -231,6 +274,27 @@ function resolveFieldTypeInternal(
|
|
|
231
274
|
return createArrayType(elementResult);
|
|
232
275
|
}
|
|
233
276
|
|
|
277
|
+
// Never type — represents an impossible value, skip this field
|
|
278
|
+
// Also handles `undefined` which results from `field?: never` (never | undefined simplifies to undefined)
|
|
279
|
+
if (type.flags & ts.TypeFlags.Never || type.flags & ts.TypeFlags.Undefined) {
|
|
280
|
+
return createNeverType();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Unknown type — represents arbitrary values, map to JSON scalar (graphql-scalars)
|
|
284
|
+
if (type.flags & ts.TypeFlags.Unknown) {
|
|
285
|
+
return createScalarType({
|
|
286
|
+
name: "JSON",
|
|
287
|
+
scalarInfo: {
|
|
288
|
+
scalarName: "JSON",
|
|
289
|
+
typeName: "unknown",
|
|
290
|
+
baseType: undefined,
|
|
291
|
+
isCustom: true,
|
|
292
|
+
only: null,
|
|
293
|
+
},
|
|
294
|
+
nullable: false,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
234
298
|
// Primitive types
|
|
235
299
|
const typeString = checker.typeToString(type);
|
|
236
300
|
|
|
@@ -247,10 +311,14 @@ function resolveFieldTypeInternal(
|
|
|
247
311
|
return createPrimitiveType({ name: "boolean", nullable: false });
|
|
248
312
|
}
|
|
249
313
|
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
250
|
-
return
|
|
314
|
+
return createStringLiteralType(typeString.replace(/"/g, ""));
|
|
251
315
|
}
|
|
252
316
|
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
253
|
-
return
|
|
317
|
+
return createNumericLiteralType(typeString);
|
|
318
|
+
}
|
|
319
|
+
// Template literal types (e.g., `prefix-${string}`) represent string subsets
|
|
320
|
+
if (type.flags & ts.TypeFlags.TemplateLiteral) {
|
|
321
|
+
return createPrimitiveType({ name: "string", nullable: false });
|
|
254
322
|
}
|
|
255
323
|
|
|
256
324
|
// Intersection types in field context
|
|
@@ -291,12 +359,69 @@ function resolveFieldTypeInternal(
|
|
|
291
359
|
});
|
|
292
360
|
}
|
|
293
361
|
|
|
294
|
-
// 4.
|
|
295
|
-
|
|
362
|
+
// 4. Register in discoveredTypes if applicable (same logic as alias expansion)
|
|
363
|
+
// This handles non-exported recursive intersection types that would otherwise
|
|
364
|
+
// cause cycle detection failures in tryExtractAsInlineObject.
|
|
365
|
+
if (type.aliasSymbol) {
|
|
366
|
+
const aliasName = type.aliasSymbol.getName();
|
|
367
|
+
if (!knownTypeNames.has(aliasName)) {
|
|
368
|
+
if (ctx.discoveredTypes && !ctx.discoveredTypes.has(aliasName)) {
|
|
369
|
+
const resolvedAliasSymbol = resolveOriginalSymbol(
|
|
370
|
+
type.aliasSymbol,
|
|
371
|
+
checker,
|
|
372
|
+
);
|
|
373
|
+
const declarations = resolvedAliasSymbol.getDeclarations();
|
|
374
|
+
const decl = declarations?.[0];
|
|
375
|
+
if (decl && !decl.getSourceFile().isDeclarationFile) {
|
|
376
|
+
const properties = type.getProperties();
|
|
377
|
+
if (properties.length > 0) {
|
|
378
|
+
const declSourceFile = decl.getSourceFile();
|
|
379
|
+
const location = getSourceLocationFromNode(decl) ?? {
|
|
380
|
+
file: declSourceFile.fileName,
|
|
381
|
+
line: 1,
|
|
382
|
+
column: 1,
|
|
383
|
+
};
|
|
384
|
+
ctx.discoveredTypes.set(aliasName, {
|
|
385
|
+
name: aliasName,
|
|
386
|
+
tsType: type,
|
|
387
|
+
tsSymbol: resolvedAliasSymbol,
|
|
388
|
+
sourceFile: relative(process.cwd(), declSourceFile.fileName),
|
|
389
|
+
sourceLocation: location,
|
|
390
|
+
});
|
|
391
|
+
return createReferenceType({ name: aliasName, nullable: false });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (ctx.discoveredTypes?.has(aliasName)) {
|
|
396
|
+
return createReferenceType({ name: aliasName, nullable: false });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 5. Otherwise, treat as inline object
|
|
402
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
296
403
|
}
|
|
297
404
|
|
|
298
405
|
// Inline object type handling
|
|
299
406
|
if (isInlineObjectType(type)) {
|
|
407
|
+
// Index signature types (Record<string, T>, { [key: string]: T })
|
|
408
|
+
// These have no named properties, only index signatures — map to JSONObject (graphql-scalars)
|
|
409
|
+
const hasStringIndex =
|
|
410
|
+
checker.getIndexTypeOfType(type, ts.IndexKind.String) !== undefined;
|
|
411
|
+
if (hasStringIndex && type.getProperties().length === 0) {
|
|
412
|
+
return createScalarType({
|
|
413
|
+
name: "JSONObject",
|
|
414
|
+
scalarInfo: {
|
|
415
|
+
scalarName: "JSONObject",
|
|
416
|
+
typeName: "Record",
|
|
417
|
+
baseType: undefined,
|
|
418
|
+
isCustom: true,
|
|
419
|
+
only: null,
|
|
420
|
+
},
|
|
421
|
+
nullable: false,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
300
425
|
// Check if typeNode references a known type
|
|
301
426
|
if (typeNode && ts.isTypeReferenceNode(typeNode)) {
|
|
302
427
|
const typeName = getTypeNameFromNode(typeNode);
|
|
@@ -309,13 +434,30 @@ function resolveFieldTypeInternal(
|
|
|
309
434
|
}
|
|
310
435
|
}
|
|
311
436
|
|
|
312
|
-
return tryExtractAsInlineObject(type, ctx);
|
|
437
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
313
438
|
}
|
|
314
439
|
|
|
315
440
|
// Mapped types (utility types like Omit, Pick, user-defined utilities)
|
|
316
441
|
if (type.flags & ts.TypeFlags.Object) {
|
|
317
442
|
const objectType = type as ts.ObjectType;
|
|
318
443
|
if (objectType.objectFlags & ts.ObjectFlags.Mapped) {
|
|
444
|
+
// Index signature mapped types (Record<string, T>) — map to JSONObject (graphql-scalars)
|
|
445
|
+
const hasMappedStringIndex =
|
|
446
|
+
checker.getIndexTypeOfType(type, ts.IndexKind.String) !== undefined;
|
|
447
|
+
if (hasMappedStringIndex && type.getProperties().length === 0) {
|
|
448
|
+
return createScalarType({
|
|
449
|
+
name: "JSONObject",
|
|
450
|
+
scalarInfo: {
|
|
451
|
+
scalarName: "JSONObject",
|
|
452
|
+
typeName: "Record",
|
|
453
|
+
baseType: undefined,
|
|
454
|
+
isCustom: true,
|
|
455
|
+
only: null,
|
|
456
|
+
},
|
|
457
|
+
nullable: false,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
319
461
|
// Check if typeNode references a known type (schema-defined type)
|
|
320
462
|
if (typeNode && ts.isTypeReferenceNode(typeNode)) {
|
|
321
463
|
const typeName = getTypeNameFromNode(typeNode);
|
|
@@ -328,7 +470,7 @@ function resolveFieldTypeInternal(
|
|
|
328
470
|
}
|
|
329
471
|
}
|
|
330
472
|
// Not a known type - treat as inline object
|
|
331
|
-
return tryExtractAsInlineObject(type, ctx);
|
|
473
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
332
474
|
}
|
|
333
475
|
}
|
|
334
476
|
|
|
@@ -348,8 +490,42 @@ function resolveFieldTypeInternal(
|
|
|
348
490
|
((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) !== 0;
|
|
349
491
|
|
|
350
492
|
if (isAnonymousObject) {
|
|
493
|
+
// Check if this type alias qualifies for transitive discovery.
|
|
494
|
+
// Type aliases to object literals used as union members should be
|
|
495
|
+
// discovered with their original alias name, not expanded as inline
|
|
496
|
+
// objects with auto-generated names. (#202)
|
|
497
|
+
if (ctx.discoveredTypes && !ctx.discoveredTypes.has(aliasName)) {
|
|
498
|
+
const resolvedAliasSymbol = resolveOriginalSymbol(
|
|
499
|
+
type.aliasSymbol,
|
|
500
|
+
checker,
|
|
501
|
+
);
|
|
502
|
+
const declarations = resolvedAliasSymbol.getDeclarations();
|
|
503
|
+
const decl = declarations?.[0];
|
|
504
|
+
if (decl && !decl.getSourceFile().isDeclarationFile) {
|
|
505
|
+
const properties = type.getProperties();
|
|
506
|
+
if (properties.length > 0) {
|
|
507
|
+
const declSourceFile = decl.getSourceFile();
|
|
508
|
+
const location = getSourceLocationFromNode(decl) ?? {
|
|
509
|
+
file: declSourceFile.fileName,
|
|
510
|
+
line: 1,
|
|
511
|
+
column: 1,
|
|
512
|
+
};
|
|
513
|
+
ctx.discoveredTypes.set(aliasName, {
|
|
514
|
+
name: aliasName,
|
|
515
|
+
tsType: type,
|
|
516
|
+
tsSymbol: resolvedAliasSymbol,
|
|
517
|
+
sourceFile: relative(process.cwd(), declSourceFile.fileName),
|
|
518
|
+
sourceLocation: location,
|
|
519
|
+
});
|
|
520
|
+
return createReferenceType({ name: aliasName, nullable: false });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (ctx.discoveredTypes?.has(aliasName)) {
|
|
525
|
+
return createReferenceType({ name: aliasName, nullable: false });
|
|
526
|
+
}
|
|
351
527
|
// Not a known schema type and is an anonymous object - expand to generate Payload type
|
|
352
|
-
return tryExtractAsInlineObject(type, ctx);
|
|
528
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
353
529
|
}
|
|
354
530
|
}
|
|
355
531
|
}
|
|
@@ -411,7 +587,7 @@ function resolveFieldTypeInternal(
|
|
|
411
587
|
return createReferenceType({ name: symbolName, nullable: false });
|
|
412
588
|
}
|
|
413
589
|
// Type from outside schema files - expand as inline object
|
|
414
|
-
return tryExtractAsInlineObject(type, ctx);
|
|
590
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
415
591
|
}
|
|
416
592
|
|
|
417
593
|
// Check for scalar base type mapping
|
|
@@ -461,6 +637,30 @@ function resolveFieldTypeInternal(
|
|
|
461
637
|
}
|
|
462
638
|
}
|
|
463
639
|
|
|
640
|
+
// Discover extractable named types for transitive type registration
|
|
641
|
+
if (ctx.discoveredTypes && !ctx.discoveredTypes.has(symbolName)) {
|
|
642
|
+
const declarations = resolvedSymbol.getDeclarations();
|
|
643
|
+
const decl = declarations?.[0];
|
|
644
|
+
if (decl && !decl.getSourceFile().isDeclarationFile) {
|
|
645
|
+
const properties = type.getProperties();
|
|
646
|
+
if (properties.length > 0) {
|
|
647
|
+
const declSourceFile = decl.getSourceFile();
|
|
648
|
+
const location = getSourceLocationFromNode(decl) ?? {
|
|
649
|
+
file: declSourceFile.fileName,
|
|
650
|
+
line: 1,
|
|
651
|
+
column: 1,
|
|
652
|
+
};
|
|
653
|
+
ctx.discoveredTypes.set(symbolName, {
|
|
654
|
+
name: symbolName,
|
|
655
|
+
tsType: type,
|
|
656
|
+
tsSymbol: resolvedSymbol,
|
|
657
|
+
sourceFile: relative(process.cwd(), declSourceFile.fileName),
|
|
658
|
+
sourceLocation: location,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
464
664
|
// Unknown type - still return reference but it will likely cause validation error later
|
|
465
665
|
return createReferenceType({ name: symbolName, nullable: false });
|
|
466
666
|
}
|
|
@@ -472,12 +672,28 @@ function resolveFieldTypeInternal(
|
|
|
472
672
|
function tryExtractAsInlineObject(
|
|
473
673
|
type: ts.Type,
|
|
474
674
|
ctx: InternalFieldTypeContext,
|
|
675
|
+
hintName: string | null,
|
|
475
676
|
): TSTypeReference {
|
|
476
677
|
const { visitedTypes, checker } = ctx;
|
|
477
678
|
if (visitedTypes.has(type)) {
|
|
478
|
-
//
|
|
479
|
-
const
|
|
480
|
-
|
|
679
|
+
// Prefer aliasSymbol (type alias name) over type.symbol (which may be __type for anonymous objects)
|
|
680
|
+
const candidateName = type.aliasSymbol?.getName() ?? type.symbol?.getName();
|
|
681
|
+
if (
|
|
682
|
+
candidateName &&
|
|
683
|
+
!isInternalTypeSymbol(candidateName) &&
|
|
684
|
+
(ctx.knownTypeNames.has(candidateName) ||
|
|
685
|
+
ctx.discoveredTypes?.has(candidateName))
|
|
686
|
+
) {
|
|
687
|
+
// Valid reference target exists in the schema
|
|
688
|
+
return createReferenceType({ name: candidateName, nullable: false });
|
|
689
|
+
}
|
|
690
|
+
// No valid reference target — emit warning and skip field
|
|
691
|
+
ctx.diagnostics.push({
|
|
692
|
+
code: "CYCLE_DETECTED",
|
|
693
|
+
message: "Cycle detected in type resolution; field will be skipped",
|
|
694
|
+
severity: "warning",
|
|
695
|
+
});
|
|
696
|
+
return createNeverType();
|
|
481
697
|
}
|
|
482
698
|
|
|
483
699
|
visitedTypes.add(type);
|
|
@@ -488,6 +704,14 @@ function tryExtractAsInlineObject(
|
|
|
488
704
|
(propType) => resolveFieldTypeInternal(propType, undefined, ctx),
|
|
489
705
|
);
|
|
490
706
|
|
|
707
|
+
// Allow this type to be visited again in sibling union members.
|
|
708
|
+
// Cycle detection still works because real cycles are caught during the
|
|
709
|
+
// recursive extractInlineObjectPropertiesShared call above (while the
|
|
710
|
+
// type is still in visitedTypes). Removing it afterward prevents
|
|
711
|
+
// false-positive cycle detection when TypeScript shares the same type
|
|
712
|
+
// object across multiple union members (common with generic .d.ts types).
|
|
713
|
+
visitedTypes.delete(type);
|
|
714
|
+
|
|
491
715
|
// Extract type-level TSDoc from the alias symbol if present (Requirement 7.2)
|
|
492
716
|
// Only extract from user-defined types, not built-in TypeScript utility types
|
|
493
717
|
let description: string | null = null;
|
|
@@ -511,6 +735,7 @@ function tryExtractAsInlineObject(
|
|
|
511
735
|
properties: inlineProperties,
|
|
512
736
|
description,
|
|
513
737
|
deprecated,
|
|
738
|
+
hintName,
|
|
514
739
|
});
|
|
515
740
|
}
|
|
516
741
|
|