@gqlkit-ts/cli 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +379 -56
- 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 +23 -2
- 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/resolver-field-iterator.d.ts +1 -1
- package/dist/auto-type-generator/resolver-field-iterator.d.ts.map +1 -1
- package/dist/auto-type-generator/resolver-field-iterator.js +3 -0
- package/dist/auto-type-generator/resolver-field-iterator.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/docs.d.ts +1 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/gen.d.ts +1 -0
- 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/commands/main.d.ts +1 -0
- package/dist/commands/main.d.ts.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 +43 -3
- package/dist/gen-orchestrator/orchestrator.js.map +1 -1
- package/dist/resolver-extractor/extract-resolvers.d.ts +4 -0
- package/dist/resolver-extractor/extract-resolvers.d.ts.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.d.ts +2 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.js +35 -6
- package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
- package/dist/resolver-extractor/index.d.ts +1 -1
- package/dist/resolver-extractor/index.d.ts.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 +24 -4
- 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 +5 -0
- package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.js +44 -3
- 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/getting-started.md +2 -1
- package/docs/index.md +2 -0
- package/docs/integration/ai-sdk.md +189 -0
- package/docs/schema/conventions.md +7 -0
- package/docs/schema/fields.md +15 -0
- package/docs/schema/queries-mutations.md +21 -2
- package/docs/schema/subscriptions.md +173 -0
- package/docs/schema/unions.md +117 -0
- package/package.json +4 -4
- package/src/auto-type-generator/auto-type-generator.ts +588 -62
- 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 +207 -3
- package/src/auto-type-generator/resolver-field-iterator.ts +5 -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 +50 -3
- package/src/resolver-extractor/extract-resolvers.ts +5 -0
- package/src/resolver-extractor/extractor/define-api-extractor.ts +47 -7
- package/src/resolver-extractor/index.ts +1 -0
- package/src/resolver-extractor/validator/abstract-resolver-validator.ts +20 -6
- package/src/schema-generator/emitter/code-emitter.ts +43 -5
- 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 +55 -2
- 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
|
@@ -34,7 +34,7 @@ export interface InputFieldContext {
|
|
|
34
34
|
*/
|
|
35
35
|
export interface ResolverArgContext {
|
|
36
36
|
readonly kind: "resolverArg";
|
|
37
|
-
readonly resolverType: "query" | "mutation" | "field";
|
|
37
|
+
readonly resolverType: "query" | "mutation" | "subscription" | "field";
|
|
38
38
|
readonly fieldName: string;
|
|
39
39
|
readonly argName: string;
|
|
40
40
|
readonly parentTypeName: string | null;
|
|
@@ -49,17 +49,42 @@ export interface ResolverArgContext {
|
|
|
49
49
|
*/
|
|
50
50
|
export interface ResolverPayloadContext {
|
|
51
51
|
readonly kind: "resolverPayload";
|
|
52
|
-
readonly resolverType: "query" | "mutation" | "field";
|
|
52
|
+
readonly resolverType: "query" | "mutation" | "subscription" | "field";
|
|
53
53
|
readonly fieldName: string;
|
|
54
54
|
readonly parentTypeName: string | null;
|
|
55
55
|
readonly fieldPath: ReadonlyArray<string>;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
const IRREGULAR_SINGULAR_FIELD_NAMES = new Map([
|
|
59
|
+
["aliases", "alias"],
|
|
60
|
+
["analyses", "analysis"],
|
|
61
|
+
["buses", "bus"],
|
|
62
|
+
["children", "child"],
|
|
63
|
+
["cookies", "cookie"],
|
|
64
|
+
["crises", "crisis"],
|
|
65
|
+
["diagnoses", "diagnosis"],
|
|
66
|
+
["feet", "foot"],
|
|
67
|
+
["geese", "goose"],
|
|
68
|
+
["men", "man"],
|
|
69
|
+
["mice", "mouse"],
|
|
70
|
+
["movies", "movie"],
|
|
71
|
+
["people", "person"],
|
|
72
|
+
["selfies", "selfie"],
|
|
73
|
+
["statuses", "status"],
|
|
74
|
+
["teeth", "tooth"],
|
|
75
|
+
["theses", "thesis"],
|
|
76
|
+
["women", "woman"],
|
|
77
|
+
["zombies", "zombie"],
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const NON_INFLECTING_FIELD_NAMES = new Set(["news", "series", "species"]);
|
|
81
|
+
const AMBIGUOUS_PLURAL_FIELD_NAMES = new Set(["axes"]);
|
|
82
|
+
|
|
58
83
|
/**
|
|
59
84
|
* Convert a string to PascalCase.
|
|
60
85
|
* Handles camelCase, snake_case, and kebab-case inputs.
|
|
61
86
|
*/
|
|
62
|
-
function toPascalCase(str: string): string {
|
|
87
|
+
export function toPascalCase(str: string): string {
|
|
63
88
|
if (str.length === 0) return str;
|
|
64
89
|
|
|
65
90
|
return str
|
|
@@ -76,6 +101,185 @@ function toPascalCase(str: string): string {
|
|
|
76
101
|
.join("");
|
|
77
102
|
}
|
|
78
103
|
|
|
104
|
+
function isConsonant(char: string): boolean {
|
|
105
|
+
return /^[bcdfghjklmnpqrstvwxyz]$/i.test(char);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isUppercaseLetter(char: string): boolean {
|
|
109
|
+
return char.toLowerCase() !== char && char.toUpperCase() === char;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function applyReplacementCase(params: {
|
|
113
|
+
readonly replacement: string;
|
|
114
|
+
readonly template: string;
|
|
115
|
+
}): string {
|
|
116
|
+
const { replacement, template } = params;
|
|
117
|
+
|
|
118
|
+
if (template.toUpperCase() === template) {
|
|
119
|
+
return replacement.toUpperCase();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (isUppercaseLetter(template.charAt(0))) {
|
|
123
|
+
return `${replacement.charAt(0).toUpperCase()}${replacement.slice(1)}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return replacement;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function singularizeIrregularFieldName(name: string): string | null {
|
|
130
|
+
const lowerName = name.toLowerCase();
|
|
131
|
+
|
|
132
|
+
for (const [plural, singular] of IRREGULAR_SINGULAR_FIELD_NAMES) {
|
|
133
|
+
if (!lowerName.endsWith(plural)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const suffixStart = name.length - plural.length;
|
|
138
|
+
if (suffixStart > 0) {
|
|
139
|
+
const previousChar = name.charAt(suffixStart - 1);
|
|
140
|
+
const suffixFirstChar = name.charAt(suffixStart);
|
|
141
|
+
const hasWordBoundary =
|
|
142
|
+
previousChar === "_" ||
|
|
143
|
+
previousChar === "-" ||
|
|
144
|
+
isUppercaseLetter(suffixFirstChar);
|
|
145
|
+
|
|
146
|
+
if (!hasWordBoundary) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return `${name.slice(0, suffixStart)}${applyReplacementCase({
|
|
152
|
+
replacement: singular,
|
|
153
|
+
template: name.slice(suffixStart),
|
|
154
|
+
})}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Singularize a plural field name conservatively for array element type naming.
|
|
162
|
+
* Falls back to the original name when the plural form is ambiguous.
|
|
163
|
+
*/
|
|
164
|
+
export function singularizeFieldName(name: string): string {
|
|
165
|
+
const irregularSingular = singularizeIrregularFieldName(name);
|
|
166
|
+
if (irregularSingular) {
|
|
167
|
+
return irregularSingular;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const lowerName = name.toLowerCase();
|
|
171
|
+
|
|
172
|
+
if (name.length <= 3 || NON_INFLECTING_FIELD_NAMES.has(lowerName)) {
|
|
173
|
+
return name;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (AMBIGUOUS_PLURAL_FIELD_NAMES.has(lowerName)) {
|
|
177
|
+
return name;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (lowerName.endsWith("ies")) {
|
|
181
|
+
if (name.length <= 4) {
|
|
182
|
+
return name.slice(0, -1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (isConsonant(lowerName.at(-4) ?? "")) {
|
|
186
|
+
return `${name.slice(0, -3)}y`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
lowerName.endsWith("sses") ||
|
|
192
|
+
lowerName.endsWith("shes") ||
|
|
193
|
+
lowerName.endsWith("ches") ||
|
|
194
|
+
lowerName.endsWith("xes") ||
|
|
195
|
+
lowerName.endsWith("zes")
|
|
196
|
+
) {
|
|
197
|
+
return name.slice(0, -2);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (
|
|
201
|
+
lowerName.endsWith("s") &&
|
|
202
|
+
!lowerName.endsWith("ss") &&
|
|
203
|
+
!lowerName.endsWith("is") &&
|
|
204
|
+
!lowerName.endsWith("us")
|
|
205
|
+
) {
|
|
206
|
+
return name.slice(0, -1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return name;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interface AppendFieldPathParams {
|
|
213
|
+
readonly parentPath: ReadonlyArray<string>;
|
|
214
|
+
readonly fieldName: string;
|
|
215
|
+
readonly singularize: boolean;
|
|
216
|
+
readonly siblingFieldNames: ReadonlySet<string> | null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function hasSiblingFieldPathCollision(params: {
|
|
220
|
+
readonly fieldName: string;
|
|
221
|
+
readonly singularFieldName: string;
|
|
222
|
+
readonly siblingFieldNames: ReadonlySet<string> | null;
|
|
223
|
+
}): boolean {
|
|
224
|
+
const { fieldName, singularFieldName, siblingFieldNames } = params;
|
|
225
|
+
|
|
226
|
+
if (!siblingFieldNames) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (const siblingFieldName of siblingFieldNames) {
|
|
231
|
+
if (siblingFieldName === fieldName) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
siblingFieldName === singularFieldName ||
|
|
237
|
+
singularizeFieldName(siblingFieldName) === singularFieldName
|
|
238
|
+
) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function resolveFieldPathSegment(params: {
|
|
247
|
+
readonly fieldName: string;
|
|
248
|
+
readonly singularize: boolean;
|
|
249
|
+
readonly siblingFieldNames: ReadonlySet<string> | null;
|
|
250
|
+
}): string {
|
|
251
|
+
const { fieldName, singularize, siblingFieldNames } = params;
|
|
252
|
+
|
|
253
|
+
if (!singularize) {
|
|
254
|
+
return fieldName;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const singularFieldName = singularizeFieldName(fieldName);
|
|
258
|
+
if (
|
|
259
|
+
singularFieldName !== fieldName &&
|
|
260
|
+
hasSiblingFieldPathCollision({
|
|
261
|
+
fieldName,
|
|
262
|
+
singularFieldName,
|
|
263
|
+
siblingFieldNames,
|
|
264
|
+
})
|
|
265
|
+
) {
|
|
266
|
+
return fieldName;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return singularFieldName;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Append a field name to an auto-type field path.
|
|
274
|
+
*/
|
|
275
|
+
export function appendFieldPath(params: AppendFieldPathParams): string[] {
|
|
276
|
+
const { parentPath, fieldName, singularize, siblingFieldNames } = params;
|
|
277
|
+
return [
|
|
278
|
+
...parentPath,
|
|
279
|
+
resolveFieldPathSegment({ fieldName, singularize, siblingFieldNames }),
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
|
|
79
283
|
/**
|
|
80
284
|
* Remove Input suffix from type name if present.
|
|
81
285
|
*/
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
GraphQLFieldDefinition,
|
|
4
4
|
} from "../resolver-extractor/index.js";
|
|
5
5
|
|
|
6
|
-
export type ResolverType = "query" | "mutation" | "field";
|
|
6
|
+
export type ResolverType = "query" | "mutation" | "field" | "subscription";
|
|
7
7
|
|
|
8
8
|
export interface ResolverFieldInfo {
|
|
9
9
|
readonly field: GraphQLFieldDefinition;
|
|
@@ -27,6 +27,10 @@ export function forEachResolverField(
|
|
|
27
27
|
visitor({ field, resolverType: "mutation", parentTypeName: null });
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
for (const field of resolversResult.subscriptionFields.fields) {
|
|
31
|
+
visitor({ field, resolverType: "subscription", parentTypeName: null });
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
for (const ext of resolversResult.typeExtensions) {
|
|
31
35
|
for (const field of ext.fields) {
|
|
32
36
|
visitor({
|
|
@@ -45,7 +45,11 @@ function extractTypenameFromFields(
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const { tsType } = field;
|
|
48
|
-
if (
|
|
48
|
+
if (
|
|
49
|
+
tsType.nullable ||
|
|
50
|
+
tsType.kind !== "stringLiteral" ||
|
|
51
|
+
tsType.name === null
|
|
52
|
+
) {
|
|
49
53
|
return null;
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -63,7 +67,11 @@ function extractTypenameFromInlineObjectProperties(
|
|
|
63
67
|
const { property, fieldName } = found;
|
|
64
68
|
const { propertyType: tsType } = property;
|
|
65
69
|
|
|
66
|
-
if (
|
|
70
|
+
if (
|
|
71
|
+
tsType.nullable ||
|
|
72
|
+
tsType.kind !== "stringLiteral" ||
|
|
73
|
+
tsType.name === null
|
|
74
|
+
) {
|
|
67
75
|
return null;
|
|
68
76
|
}
|
|
69
77
|
|
|
@@ -206,12 +214,14 @@ function extractTypenames(
|
|
|
206
214
|
export interface CollectTypenameExtractionsParams {
|
|
207
215
|
readonly extractedTypes: ReadonlyArray<ExtractedTypeInfo>;
|
|
208
216
|
readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
|
|
217
|
+
/** Union names that have discriminatorFields configured; these are excluded from typename extraction. */
|
|
218
|
+
readonly discriminatorFieldUnionNames: ReadonlySet<string>;
|
|
209
219
|
}
|
|
210
220
|
|
|
211
221
|
export function collectTypenameExtractions(
|
|
212
222
|
params: CollectTypenameExtractionsParams,
|
|
213
223
|
): ReadonlyArray<TypenameExtractionResult> {
|
|
214
|
-
const { extractedTypes, typeMap } = params;
|
|
224
|
+
const { extractedTypes, typeMap, discriminatorFieldUnionNames } = params;
|
|
215
225
|
const results: TypenameExtractionResult[] = [];
|
|
216
226
|
|
|
217
227
|
for (const typeInfo of extractedTypes) {
|
|
@@ -219,6 +229,10 @@ export function collectTypenameExtractions(
|
|
|
219
229
|
typeInfo.metadata.kind === "union" ||
|
|
220
230
|
typeInfo.metadata.kind === "graphqlInterface"
|
|
221
231
|
) {
|
|
232
|
+
// Skip unions that have discriminatorFields configured; they use the discriminator pipeline instead
|
|
233
|
+
if (discriminatorFieldUnionNames.has(typeInfo.metadata.name)) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
222
236
|
const result = extractTypenames({ abstractType: typeInfo, typeMap });
|
|
223
237
|
if (result !== null) {
|
|
224
238
|
results.push(result);
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
InlineObjectProperty,
|
|
5
|
-
} from "../type-extractor/types/index.js";
|
|
6
|
-
import type {
|
|
7
|
-
AutoGeneratedField,
|
|
8
|
-
AutoGeneratedType,
|
|
9
|
-
} from "./auto-type-generator.js";
|
|
1
|
+
import type { ExtractedTypeInfo } from "../type-extractor/types/index.js";
|
|
2
|
+
import type { AutoGeneratedType } from "./auto-type-generator.js";
|
|
3
|
+
import { generateObjectTypeFromInlineObject } from "./inline-object-converter.js";
|
|
10
4
|
import type { ResolveTypeFieldPattern } from "./resolve-type-generator.js";
|
|
11
5
|
import {
|
|
12
6
|
collectTypenameExtractions,
|
|
@@ -39,6 +33,8 @@ export interface CollectTypenameResolveTypesParams {
|
|
|
39
33
|
readonly extractedTypes: ReadonlyArray<ExtractedTypeInfo>;
|
|
40
34
|
readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
|
|
41
35
|
readonly manualResolveTypeNames: ReadonlySet<string>;
|
|
36
|
+
/** Union names that have discriminatorFields configured; these are excluded from typename processing. */
|
|
37
|
+
readonly discriminatorFieldUnionNames: ReadonlySet<string>;
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
export interface CollectTypenameResolveTypesResult {
|
|
@@ -83,100 +79,8 @@ function determineResolveTypePattern(
|
|
|
83
79
|
return { usedFieldNames, memberFieldMap };
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
case "string":
|
|
89
|
-
return "String";
|
|
90
|
-
case "number":
|
|
91
|
-
return "Float";
|
|
92
|
-
case "boolean":
|
|
93
|
-
return "Boolean";
|
|
94
|
-
default:
|
|
95
|
-
return "String";
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function convertInlineObjectPropertyToField(
|
|
100
|
-
property: InlineObjectProperty,
|
|
101
|
-
): AutoGeneratedField | null {
|
|
102
|
-
if (
|
|
103
|
-
property.propertyName === "__typename" ||
|
|
104
|
-
property.propertyName === "$typeName"
|
|
105
|
-
) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const tsType = property.propertyType;
|
|
110
|
-
let typeName: string;
|
|
111
|
-
const nullable = tsType.nullable;
|
|
112
|
-
let list = false;
|
|
113
|
-
let listItemNullable: boolean | null = null;
|
|
114
|
-
|
|
115
|
-
if (tsType.kind === "array" && tsType.elementType) {
|
|
116
|
-
list = true;
|
|
117
|
-
listItemNullable = tsType.elementType.nullable;
|
|
118
|
-
const elementName = tsType.elementType.name ?? "String";
|
|
119
|
-
typeName =
|
|
120
|
-
tsType.elementType.kind === "primitive"
|
|
121
|
-
? primitiveToGraphQLScalar(elementName)
|
|
122
|
-
: elementName;
|
|
123
|
-
} else if (tsType.kind === "reference" && tsType.name) {
|
|
124
|
-
typeName = tsType.name;
|
|
125
|
-
} else if (tsType.kind === "primitive" && tsType.name) {
|
|
126
|
-
typeName = primitiveToGraphQLScalar(tsType.name);
|
|
127
|
-
} else if (tsType.kind === "scalar" && tsType.scalarInfo) {
|
|
128
|
-
typeName = tsType.scalarInfo.scalarName;
|
|
129
|
-
} else {
|
|
130
|
-
typeName = "String";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
name: property.propertyName,
|
|
135
|
-
type: {
|
|
136
|
-
typeName,
|
|
137
|
-
nullable,
|
|
138
|
-
list,
|
|
139
|
-
listItemNullable,
|
|
140
|
-
},
|
|
141
|
-
description: property.description,
|
|
142
|
-
deprecated: property.deprecated,
|
|
143
|
-
directives: null,
|
|
144
|
-
defaultValue: null,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function generateObjectTypeFromInlineObject(
|
|
149
|
-
inlineObjectMember: InlineObjectMember,
|
|
150
|
-
typeName: string,
|
|
151
|
-
abstractTypeName: string,
|
|
152
|
-
sourceFile: string,
|
|
153
|
-
): AutoGeneratedType {
|
|
154
|
-
const fields: AutoGeneratedField[] = [];
|
|
155
|
-
|
|
156
|
-
for (const property of inlineObjectMember.properties) {
|
|
157
|
-
const field = convertInlineObjectPropertyToField(property);
|
|
158
|
-
if (field !== null) {
|
|
159
|
-
fields.push(field);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
name: typeName,
|
|
165
|
-
kind: "Object",
|
|
166
|
-
fields,
|
|
167
|
-
enumValues: null,
|
|
168
|
-
unionMembers: null,
|
|
169
|
-
needsStringEnumMapping: false,
|
|
170
|
-
sourceLocation: { file: sourceFile, line: 1, column: 1 },
|
|
171
|
-
generatedFrom: {
|
|
172
|
-
parentTypeName: abstractTypeName,
|
|
173
|
-
fieldPath: [],
|
|
174
|
-
context: "typeField",
|
|
175
|
-
},
|
|
176
|
-
description: null,
|
|
177
|
-
resolveTypeFieldPattern: null,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
82
|
+
/** Typename-related properties that should be excluded from generated object types. */
|
|
83
|
+
const TYPENAME_PROPERTY_NAMES = new Set(["__typename", "$typeName"]);
|
|
180
84
|
|
|
181
85
|
function collectGeneratedObjectTypes(
|
|
182
86
|
extraction: TypenameExtractionResult,
|
|
@@ -209,12 +113,13 @@ function collectGeneratedObjectTypes(
|
|
|
209
113
|
|
|
210
114
|
const typeName = member.typenameInfo.typeName;
|
|
211
115
|
|
|
212
|
-
const objectType = generateObjectTypeFromInlineObject(
|
|
116
|
+
const objectType = generateObjectTypeFromInlineObject({
|
|
213
117
|
inlineObjectMember,
|
|
214
118
|
typeName,
|
|
215
|
-
extraction.abstractTypeName,
|
|
216
|
-
extractedType.metadata.sourceFile,
|
|
217
|
-
|
|
119
|
+
abstractTypeName: extraction.abstractTypeName,
|
|
120
|
+
sourceFile: extractedType.metadata.sourceFile,
|
|
121
|
+
skipPropertyNames: TYPENAME_PROPERTY_NAMES,
|
|
122
|
+
});
|
|
218
123
|
|
|
219
124
|
generatedObjectTypes.push(objectType);
|
|
220
125
|
generatedInlineObjectTypes.push({
|
|
@@ -230,11 +135,17 @@ function collectGeneratedObjectTypes(
|
|
|
230
135
|
export function collectTypenameResolveTypes(
|
|
231
136
|
params: CollectTypenameResolveTypesParams,
|
|
232
137
|
): CollectTypenameResolveTypesResult {
|
|
233
|
-
const {
|
|
138
|
+
const {
|
|
139
|
+
extractedTypes,
|
|
140
|
+
typeMap,
|
|
141
|
+
manualResolveTypeNames,
|
|
142
|
+
discriminatorFieldUnionNames,
|
|
143
|
+
} = params;
|
|
234
144
|
|
|
235
145
|
const extractions = collectTypenameExtractions({
|
|
236
146
|
extractedTypes,
|
|
237
147
|
typeMap,
|
|
148
|
+
discriminatorFieldUnionNames,
|
|
238
149
|
});
|
|
239
150
|
|
|
240
151
|
const autoResolveTypes: TypenameAutoResolveTypeInfo[] = [];
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
*/
|
|
16
16
|
export const TYPENAME_FIELD_NAMES = ["__typename", "$typeName"] as const;
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Check if a field name is a typename discrimination field.
|
|
20
|
+
*/
|
|
21
|
+
export function isTypenameFieldName(name: string): boolean {
|
|
22
|
+
return (TYPENAME_FIELD_NAMES as readonly string[]).includes(name);
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
/**
|
|
19
26
|
* The field name used for type discrimination.
|
|
20
27
|
*/
|
|
@@ -74,7 +74,7 @@ function analyzeInlineObjectTypename(
|
|
|
74
74
|
return {
|
|
75
75
|
exists: true,
|
|
76
76
|
fieldName,
|
|
77
|
-
isStringLiteral: tsType.kind === "
|
|
77
|
+
isStringLiteral: tsType.kind === "stringLiteral" && tsType.name !== null,
|
|
78
78
|
isNullable: tsType.nullable,
|
|
79
79
|
};
|
|
80
80
|
}
|
|
@@ -171,7 +171,8 @@ export function validateTypenames(
|
|
|
171
171
|
|
|
172
172
|
const { typeName, fieldName } = member.typenameInfo;
|
|
173
173
|
const memberTypeName =
|
|
174
|
-
member.memberTypeName ??
|
|
174
|
+
member.memberTypeName ??
|
|
175
|
+
`(anonymous member at index ${member.memberIndex})`;
|
|
175
176
|
|
|
176
177
|
const existing = typenameValueToMembers.get(typeName) ?? [];
|
|
177
178
|
existing.push({ memberTypeName, fieldName });
|
|
@@ -238,7 +239,7 @@ function extractTypenameFromObjectType(
|
|
|
238
239
|
if (
|
|
239
240
|
field.optional ||
|
|
240
241
|
tsType.nullable ||
|
|
241
|
-
tsType.kind !== "
|
|
242
|
+
tsType.kind !== "stringLiteral" ||
|
|
242
243
|
tsType.name === null
|
|
243
244
|
) {
|
|
244
245
|
return null;
|
package/src/commands/gen.ts
CHANGED
|
@@ -45,8 +45,14 @@ export async function runGenCommand(
|
|
|
45
45
|
? dirname(configResult.configPath)
|
|
46
46
|
: options.cwd;
|
|
47
47
|
|
|
48
|
-
const {
|
|
49
|
-
|
|
48
|
+
const {
|
|
49
|
+
sourceDir,
|
|
50
|
+
sourceIgnoreGlobs,
|
|
51
|
+
output,
|
|
52
|
+
scalars,
|
|
53
|
+
tsconfigPath,
|
|
54
|
+
discriminatorFields,
|
|
55
|
+
} = configResult.config;
|
|
50
56
|
|
|
51
57
|
const config: GenerationConfig = {
|
|
52
58
|
cwd: options.cwd,
|
|
@@ -56,6 +62,7 @@ export async function runGenCommand(
|
|
|
56
62
|
configDir,
|
|
57
63
|
customScalars: scalars,
|
|
58
64
|
tsconfigPath,
|
|
65
|
+
discriminatorFields,
|
|
59
66
|
};
|
|
60
67
|
|
|
61
68
|
progressReporter.startPhase("Extracting types");
|
package/src/config/types.ts
CHANGED
|
@@ -100,6 +100,16 @@ export interface GqlkitConfig {
|
|
|
100
100
|
* Hook configuration for lifecycle events.
|
|
101
101
|
*/
|
|
102
102
|
readonly hooks?: HooksConfig;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Custom discriminator field mappings for union types.
|
|
106
|
+
* Maps GraphQL union type names to TypeScript discriminator field names.
|
|
107
|
+
* When specified, gqlkit generates __resolveType functions based on these fields
|
|
108
|
+
* instead of requiring $typeName or __typename.
|
|
109
|
+
*/
|
|
110
|
+
readonly discriminatorFields?: Readonly<
|
|
111
|
+
Record<string, string | ReadonlyArray<string>>
|
|
112
|
+
>;
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
/**
|
|
@@ -42,6 +42,15 @@ export interface ResolvedHooksConfig {
|
|
|
42
42
|
readonly afterAllFileWrite: ReadonlyArray<string>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Normalized discriminator fields mapping.
|
|
47
|
+
* All values are normalized to arrays (single strings are wrapped in arrays).
|
|
48
|
+
*/
|
|
49
|
+
export type ResolvedDiscriminatorFieldsMap = ReadonlyMap<
|
|
50
|
+
string,
|
|
51
|
+
ReadonlyArray<string>
|
|
52
|
+
>;
|
|
53
|
+
|
|
45
54
|
export interface ResolvedConfig {
|
|
46
55
|
readonly sourceDir: string;
|
|
47
56
|
readonly sourceIgnoreGlobs: ReadonlyArray<string>;
|
|
@@ -49,6 +58,7 @@ export interface ResolvedConfig {
|
|
|
49
58
|
readonly scalars: ReadonlyArray<ResolvedScalarMapping>;
|
|
50
59
|
readonly tsconfigPath: string | null;
|
|
51
60
|
readonly hooks: ResolvedHooksConfig;
|
|
61
|
+
readonly discriminatorFields: ResolvedDiscriminatorFieldsMap;
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
export interface LoadConfigResult {
|
|
@@ -82,6 +92,7 @@ const DEFAULT_RESOLVED_CONFIG: ResolvedConfig = {
|
|
|
82
92
|
scalars: [],
|
|
83
93
|
tsconfigPath: null,
|
|
84
94
|
hooks: DEFAULT_HOOKS_CONFIG,
|
|
95
|
+
discriminatorFields: new Map(),
|
|
85
96
|
};
|
|
86
97
|
|
|
87
98
|
export async function loadConfig(
|