@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.
- package/dist/auto-type-generator/auto-type-generator.d.ts +10 -4
- package/dist/auto-type-generator/auto-type-generator.d.ts.map +1 -1
- package/dist/auto-type-generator/auto-type-generator.js +640 -133
- package/dist/auto-type-generator/auto-type-generator.js.map +1 -1
- package/dist/auto-type-generator/index.d.ts +8 -1
- 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 +13 -5
- package/dist/auto-type-generator/inline-enum-collector.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-enum-collector.js +107 -71
- package/dist/auto-type-generator/inline-enum-collector.js.map +1 -1
- package/dist/auto-type-generator/inline-object-traverser.d.ts +20 -0
- package/dist/auto-type-generator/inline-object-traverser.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-object-traverser.js +22 -0
- package/dist/auto-type-generator/inline-object-traverser.js.map +1 -0
- package/dist/auto-type-generator/inline-union-collector.d.ts +29 -0
- package/dist/auto-type-generator/inline-union-collector.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-union-collector.js +216 -0
- package/dist/auto-type-generator/inline-union-collector.js.map +1 -0
- package/dist/auto-type-generator/inline-union-types.d.ts +29 -0
- package/dist/auto-type-generator/inline-union-types.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-union-types.js +2 -0
- package/dist/auto-type-generator/inline-union-types.js.map +1 -0
- package/dist/auto-type-generator/inline-union-validator.d.ts +76 -0
- package/dist/auto-type-generator/inline-union-validator.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-union-validator.js +329 -0
- package/dist/auto-type-generator/inline-union-validator.js.map +1 -0
- package/dist/auto-type-generator/naming-convention.d.ts +18 -1
- package/dist/auto-type-generator/naming-convention.d.ts.map +1 -1
- package/dist/auto-type-generator/naming-convention.js +16 -0
- package/dist/auto-type-generator/naming-convention.js.map +1 -1
- package/dist/auto-type-generator/resolve-type-generator.d.ts +20 -0
- package/dist/auto-type-generator/resolve-type-generator.d.ts.map +1 -0
- package/dist/auto-type-generator/resolve-type-generator.js +2 -0
- package/dist/auto-type-generator/resolve-type-generator.js.map +1 -0
- package/dist/auto-type-generator/resolver-field-iterator.d.ts +13 -0
- package/dist/auto-type-generator/resolver-field-iterator.d.ts.map +1 -0
- package/dist/auto-type-generator/resolver-field-iterator.js +22 -0
- package/dist/auto-type-generator/resolver-field-iterator.js.map +1 -0
- package/dist/auto-type-generator/typename-extractor.d.ts +26 -0
- package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -0
- package/dist/auto-type-generator/typename-extractor.js +142 -0
- package/dist/auto-type-generator/typename-extractor.js.map +1 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +35 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.js +177 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -0
- package/dist/auto-type-generator/typename-types.d.ts +43 -0
- package/dist/auto-type-generator/typename-types.d.ts.map +1 -0
- package/dist/auto-type-generator/typename-types.js +37 -0
- package/dist/auto-type-generator/typename-types.js.map +1 -0
- package/dist/auto-type-generator/typename-validator.d.ts +37 -0
- package/dist/auto-type-generator/typename-validator.d.ts.map +1 -0
- package/dist/auto-type-generator/typename-validator.js +206 -0
- package/dist/auto-type-generator/typename-validator.js.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/docs.d.ts +51 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +154 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/gen-orchestrator/orchestrator.js +13 -6
- package/dist/gen-orchestrator/orchestrator.js.map +1 -1
- package/dist/resolver-extractor/extract-resolvers.d.ts +19 -1
- package/dist/resolver-extractor/extract-resolvers.d.ts.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.d.ts +5 -0
- package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.js +14 -61
- package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
- package/dist/resolver-extractor/index.d.ts +0 -1
- package/dist/resolver-extractor/index.d.ts.map +1 -1
- package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +1 -0
- package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts.map +1 -1
- package/dist/resolver-extractor/validator/abstract-resolver-validator.js +9 -5
- 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 +20 -0
- package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
- package/dist/schema-generator/generate-schema.d.ts +1 -0
- package/dist/schema-generator/generate-schema.d.ts.map +1 -1
- package/dist/schema-generator/generate-schema.js +72 -3
- package/dist/schema-generator/generate-schema.js.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.d.ts +14 -2
- package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.js +54 -1
- 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 +22 -0
- package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
- package/dist/shared/enum-prefix-detector.d.ts +63 -0
- package/dist/shared/enum-prefix-detector.d.ts.map +1 -0
- package/dist/shared/enum-prefix-detector.js +80 -0
- package/dist/shared/enum-prefix-detector.js.map +1 -0
- package/dist/shared/ignore-fields-detector.d.ts +26 -0
- package/dist/shared/ignore-fields-detector.d.ts.map +1 -0
- package/dist/shared/ignore-fields-detector.js +83 -0
- package/dist/shared/ignore-fields-detector.js.map +1 -0
- package/dist/shared/ignore-fields-validator.d.ts +29 -0
- package/dist/shared/ignore-fields-validator.d.ts.map +1 -0
- package/dist/shared/ignore-fields-validator.js +43 -0
- package/dist/shared/ignore-fields-validator.js.map +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/source-location.d.ts +5 -0
- package/dist/shared/source-location.d.ts.map +1 -1
- package/dist/shared/source-location.js +7 -0
- package/dist/shared/source-location.js.map +1 -1
- package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
- package/dist/type-extractor/converter/graphql-converter.js +21 -7
- package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
- package/dist/type-extractor/extractor/field-type-resolver.js +42 -3
- package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -1
- package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor/extractor/type-extractor.js +88 -23
- 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/ts-type-reference-factory.d.ts +10 -2
- package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -1
- package/dist/type-extractor/types/ts-type-reference-factory.js +8 -2
- package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -1
- package/dist/type-extractor/types/typescript.d.ts +4 -0
- package/dist/type-extractor/types/typescript.d.ts.map +1 -1
- package/docs/coding-agents.md +64 -0
- package/docs/configuration.md +6 -20
- package/docs/getting-started.md +15 -12
- package/docs/index.md +36 -22
- package/docs/integration/apollo.md +8 -40
- package/docs/integration/drizzle.md +6 -10
- package/docs/integration/prisma.md +196 -0
- package/docs/integration/yoga.md +8 -40
- package/docs/schema/abstract-resolvers.md +117 -0
- package/docs/schema/directives.md +5 -0
- package/docs/schema/documentation.md +5 -0
- package/docs/schema/enums.md +99 -0
- package/docs/schema/fields.md +64 -0
- package/docs/schema/index.md +21 -0
- package/docs/schema/inputs.md +115 -15
- package/docs/schema/interfaces.md +31 -1
- package/docs/schema/objects.md +40 -0
- package/docs/schema/queries-mutations.md +136 -22
- package/docs/schema/scalars.md +5 -0
- package/docs/schema/unions.md +208 -1
- package/docs/what-is-gqlkit.md +13 -8
- package/package.json +6 -4
- package/src/auto-type-generator/auto-type-generator.ts +946 -201
- package/src/auto-type-generator/index.ts +42 -0
- package/src/auto-type-generator/inline-enum-collector.ts +187 -139
- package/src/auto-type-generator/inline-object-traverser.ts +49 -0
- package/src/auto-type-generator/inline-union-collector.ts +402 -0
- package/src/auto-type-generator/inline-union-types.ts +33 -0
- package/src/auto-type-generator/inline-union-validator.ts +482 -0
- package/src/auto-type-generator/naming-convention.ts +38 -1
- package/src/auto-type-generator/resolve-type-generator.ts +21 -0
- package/src/auto-type-generator/resolver-field-iterator.ts +39 -0
- package/src/auto-type-generator/typename-extractor.ts +230 -0
- package/src/auto-type-generator/typename-resolve-type-generator.ts +281 -0
- package/src/auto-type-generator/typename-types.ts +66 -0
- package/src/auto-type-generator/typename-validator.ts +326 -0
- package/src/cli.ts +2 -0
- package/src/commands/docs.ts +211 -0
- package/src/gen-orchestrator/orchestrator.ts +20 -6
- package/src/resolver-extractor/extract-resolvers.ts +19 -0
- package/src/resolver-extractor/extractor/define-api-extractor.ts +23 -89
- package/src/resolver-extractor/index.ts +0 -6
- package/src/resolver-extractor/validator/abstract-resolver-validator.ts +16 -8
- package/src/schema-generator/emitter/code-emitter.ts +34 -0
- package/src/schema-generator/generate-schema.ts +99 -2
- package/src/schema-generator/integrator/result-integrator.ts +70 -1
- package/src/schema-generator/resolver-collector/resolver-collector.ts +34 -0
- package/src/shared/enum-prefix-detector.ts +99 -0
- package/src/shared/ignore-fields-detector.ts +109 -0
- package/src/shared/ignore-fields-validator.ts +66 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/source-location.ts +11 -0
- package/src/type-extractor/converter/graphql-converter.ts +31 -7
- package/src/type-extractor/extractor/field-type-resolver.ts +48 -3
- package/src/type-extractor/extractor/type-extractor.ts +103 -26
- package/src/type-extractor/types/diagnostics.ts +12 -2
- package/src/type-extractor/types/ts-type-reference-factory.ts +18 -5
- package/src/type-extractor/types/typescript.ts +4 -0
- package/dist/resolver-extractor/validator/only-validator.d.ts +0 -61
- package/dist/resolver-extractor/validator/only-validator.d.ts.map +0 -1
- package/dist/resolver-extractor/validator/only-validator.js +0 -76
- package/dist/resolver-extractor/validator/only-validator.js.map +0 -1
- package/src/resolver-extractor/validator/only-validator.ts +0 -158
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Diagnostic,
|
|
3
|
+
ExtractedTypeInfo,
|
|
4
|
+
SourceLocation,
|
|
5
|
+
TSTypeReference,
|
|
6
|
+
} from "../type-extractor/types/index.js";
|
|
7
|
+
import type { InlineUnionMemberInfo } from "./inline-union-types.js";
|
|
8
|
+
import { isInputTypeName } from "./naming-convention.js";
|
|
9
|
+
import {
|
|
10
|
+
findTypenameProperty,
|
|
11
|
+
type TypenameFieldName,
|
|
12
|
+
} from "./typename-types.js";
|
|
13
|
+
|
|
14
|
+
export interface ValidateUnionResult {
|
|
15
|
+
readonly valid: boolean;
|
|
16
|
+
readonly diagnostics: ReadonlyArray<Diagnostic>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ValidateUnionMembersParams {
|
|
20
|
+
readonly members: ReadonlyArray<InlineUnionMemberInfo>;
|
|
21
|
+
readonly typeName: string;
|
|
22
|
+
readonly sourceLocation: SourceLocation;
|
|
23
|
+
readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validates that all union members are object types.
|
|
28
|
+
* GraphQL unions cannot contain primitives, enums, or scalars.
|
|
29
|
+
*
|
|
30
|
+
* Reports:
|
|
31
|
+
* - INLINE_UNION_PRIMITIVE_MEMBER for primitive types
|
|
32
|
+
* - INLINE_UNION_ENUM_MEMBER for enum types
|
|
33
|
+
* - INLINE_UNION_UNRESOLVABLE_MEMBER for unresolvable types
|
|
34
|
+
*/
|
|
35
|
+
export function validateUnionMembers(
|
|
36
|
+
params: ValidateUnionMembersParams,
|
|
37
|
+
): ValidateUnionResult {
|
|
38
|
+
const { members, typeName, sourceLocation, typeMap } = params;
|
|
39
|
+
const diagnostics: Diagnostic[] = [];
|
|
40
|
+
|
|
41
|
+
for (const member of members) {
|
|
42
|
+
const memberType = member.memberType;
|
|
43
|
+
const diagnostic = validateUnionMemberType({
|
|
44
|
+
memberType,
|
|
45
|
+
needsAutoGeneration: member.needsAutoGeneration,
|
|
46
|
+
typeName,
|
|
47
|
+
sourceLocation,
|
|
48
|
+
typeMap,
|
|
49
|
+
});
|
|
50
|
+
if (diagnostic) {
|
|
51
|
+
diagnostics.push(diagnostic);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
valid: diagnostics.length === 0,
|
|
57
|
+
diagnostics,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ValidateMemberTypeParams {
|
|
62
|
+
readonly memberType: TSTypeReference;
|
|
63
|
+
readonly needsAutoGeneration: boolean;
|
|
64
|
+
readonly typeName: string;
|
|
65
|
+
readonly sourceLocation: SourceLocation;
|
|
66
|
+
readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateUnionMemberType(
|
|
70
|
+
params: ValidateMemberTypeParams,
|
|
71
|
+
): Diagnostic | null {
|
|
72
|
+
const { memberType, needsAutoGeneration, typeName, sourceLocation, typeMap } =
|
|
73
|
+
params;
|
|
74
|
+
|
|
75
|
+
if (memberType.kind === "primitive") {
|
|
76
|
+
return {
|
|
77
|
+
code: "INLINE_UNION_PRIMITIVE_MEMBER",
|
|
78
|
+
message: `Inline union '${typeName}' contains primitive type '${memberType.name}'. GraphQL unions can only contain object types.`,
|
|
79
|
+
severity: "error",
|
|
80
|
+
location: sourceLocation,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (memberType.kind === "inlineEnum") {
|
|
85
|
+
return {
|
|
86
|
+
code: "INLINE_UNION_ENUM_MEMBER",
|
|
87
|
+
message: `Inline union '${typeName}' contains an enum type. GraphQL unions can only contain object types.`,
|
|
88
|
+
severity: "error",
|
|
89
|
+
location: sourceLocation,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (memberType.kind === "literal") {
|
|
94
|
+
return {
|
|
95
|
+
code: "INLINE_UNION_ENUM_MEMBER",
|
|
96
|
+
message: `Inline union '${typeName}' contains a literal type '${memberType.name}'. GraphQL unions can only contain object types.`,
|
|
97
|
+
severity: "error",
|
|
98
|
+
location: sourceLocation,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (memberType.kind === "scalar") {
|
|
103
|
+
return {
|
|
104
|
+
code: "INLINE_UNION_UNRESOLVABLE_MEMBER",
|
|
105
|
+
message: `Inline union '${typeName}' contains scalar type '${memberType.scalarInfo?.scalarName ?? memberType.name}'. GraphQL unions can only contain object types.`,
|
|
106
|
+
severity: "error",
|
|
107
|
+
location: sourceLocation,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (memberType.kind === "reference" && memberType.name !== null) {
|
|
112
|
+
const referencedType = typeMap.get(memberType.name);
|
|
113
|
+
|
|
114
|
+
if (referencedType?.metadata.kind === "enum") {
|
|
115
|
+
return {
|
|
116
|
+
code: "INLINE_UNION_ENUM_MEMBER",
|
|
117
|
+
message: `Inline union '${typeName}' contains enum type '${memberType.name}'. GraphQL unions can only contain object types.`,
|
|
118
|
+
severity: "error",
|
|
119
|
+
location: sourceLocation,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!referencedType && needsAutoGeneration) {
|
|
124
|
+
return {
|
|
125
|
+
code: "INLINE_UNION_UNRESOLVABLE_MEMBER",
|
|
126
|
+
message: `Inline union '${typeName}' contains unresolvable type '${memberType.name}'. The type cannot be expanded as an object type.`,
|
|
127
|
+
severity: "error",
|
|
128
|
+
location: sourceLocation,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
memberType.kind === "inlineObject" ||
|
|
135
|
+
memberType.kind === "reference" ||
|
|
136
|
+
memberType.kind === "union"
|
|
137
|
+
) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
code: "INLINE_UNION_UNRESOLVABLE_MEMBER",
|
|
143
|
+
message: `Inline union '${typeName}' contains a type that cannot be resolved as an object type.`,
|
|
144
|
+
severity: "error",
|
|
145
|
+
location: sourceLocation,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ValidateOneOfMembersParams {
|
|
150
|
+
readonly members: ReadonlyArray<InlineUnionMemberInfo>;
|
|
151
|
+
readonly typeName: string;
|
|
152
|
+
readonly sourceLocation: SourceLocation;
|
|
153
|
+
readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validates @oneOf input object constraints:
|
|
158
|
+
* - Each member must have exactly one property
|
|
159
|
+
* - Property names must be unique across all members
|
|
160
|
+
* - Field types must be scalar, enum, or input object
|
|
161
|
+
*
|
|
162
|
+
* Reports:
|
|
163
|
+
* - ONEOF_EMPTY_OBJECT for empty object members
|
|
164
|
+
* - ONEOF_MULTIPLE_PROPERTIES for members with multiple properties
|
|
165
|
+
* - ONEOF_DUPLICATE_PROPERTY for duplicate property names
|
|
166
|
+
* - ONEOF_INVALID_FIELD_TYPE for invalid field types
|
|
167
|
+
*/
|
|
168
|
+
export function validateOneOfMembers(
|
|
169
|
+
params: ValidateOneOfMembersParams,
|
|
170
|
+
): ValidateUnionResult {
|
|
171
|
+
const { members, typeName, sourceLocation, typeMap } = params;
|
|
172
|
+
const diagnostics: Diagnostic[] = [];
|
|
173
|
+
const seenPropertyNames = new Set<string>();
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < members.length; i++) {
|
|
176
|
+
const member = members[i]!;
|
|
177
|
+
const memberType = member.memberType;
|
|
178
|
+
|
|
179
|
+
if (memberType.kind === "reference") {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
memberType.kind !== "inlineObject" ||
|
|
185
|
+
!memberType.inlineObjectProperties
|
|
186
|
+
) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const properties = memberType.inlineObjectProperties;
|
|
191
|
+
|
|
192
|
+
if (properties.length === 0) {
|
|
193
|
+
diagnostics.push({
|
|
194
|
+
code: "ONEOF_EMPTY_OBJECT",
|
|
195
|
+
message: `OneOf input '${typeName}' member at index ${i} is an empty object. Each member must have exactly one property.`,
|
|
196
|
+
severity: "error",
|
|
197
|
+
location: sourceLocation,
|
|
198
|
+
});
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (properties.length > 1) {
|
|
203
|
+
diagnostics.push({
|
|
204
|
+
code: "ONEOF_MULTIPLE_PROPERTIES",
|
|
205
|
+
message: `OneOf input '${typeName}' member at index ${i} has ${properties.length} properties. Each member must have exactly one property.`,
|
|
206
|
+
severity: "error",
|
|
207
|
+
location: sourceLocation,
|
|
208
|
+
});
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const prop = properties[0]!;
|
|
213
|
+
|
|
214
|
+
if (seenPropertyNames.has(prop.name)) {
|
|
215
|
+
diagnostics.push({
|
|
216
|
+
code: "ONEOF_DUPLICATE_PROPERTY",
|
|
217
|
+
message: `OneOf input '${typeName}' has duplicate property name '${prop.name}'.`,
|
|
218
|
+
severity: "error",
|
|
219
|
+
location: sourceLocation,
|
|
220
|
+
});
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
seenPropertyNames.add(prop.name);
|
|
224
|
+
|
|
225
|
+
const fieldTypeError = validateOneOfFieldType({
|
|
226
|
+
propertyName: prop.name,
|
|
227
|
+
propertyType: prop.tsType,
|
|
228
|
+
typeName,
|
|
229
|
+
sourceLocation,
|
|
230
|
+
typeMap,
|
|
231
|
+
});
|
|
232
|
+
if (fieldTypeError) {
|
|
233
|
+
diagnostics.push(fieldTypeError);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
valid: diagnostics.length === 0,
|
|
239
|
+
diagnostics,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
interface ValidateOneOfFieldTypeParams {
|
|
244
|
+
readonly propertyName: string;
|
|
245
|
+
readonly propertyType: TSTypeReference;
|
|
246
|
+
readonly typeName: string;
|
|
247
|
+
readonly sourceLocation: SourceLocation;
|
|
248
|
+
readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function validateOneOfFieldType(
|
|
252
|
+
params: ValidateOneOfFieldTypeParams,
|
|
253
|
+
): Diagnostic | null {
|
|
254
|
+
const { propertyName, propertyType, typeName, sourceLocation, typeMap } =
|
|
255
|
+
params;
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
propertyType.kind === "primitive" ||
|
|
259
|
+
propertyType.kind === "scalar" ||
|
|
260
|
+
propertyType.kind === "inlineEnum"
|
|
261
|
+
) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (propertyType.kind === "inlineObject") {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (propertyType.kind === "reference" && propertyType.name !== null) {
|
|
270
|
+
const referencedType = typeMap.get(propertyType.name);
|
|
271
|
+
|
|
272
|
+
if (!referencedType) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const isValidType =
|
|
277
|
+
referencedType.metadata.kind === "enum" ||
|
|
278
|
+
isInputTypeName(referencedType.metadata.name);
|
|
279
|
+
|
|
280
|
+
if (isValidType) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
code: "ONEOF_INVALID_FIELD_TYPE",
|
|
286
|
+
message: `OneOf input '${typeName}' field '${propertyName}' has invalid type '${propertyType.name}'. Only scalar types, enum types, and Input Object types are allowed.`,
|
|
287
|
+
severity: "error",
|
|
288
|
+
location: sourceLocation,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface ValidateUnionMemberTypenamesParams {
|
|
296
|
+
readonly members: ReadonlyArray<InlineUnionMemberInfo>;
|
|
297
|
+
readonly unionTypeName: string;
|
|
298
|
+
readonly sourceLocation: SourceLocation;
|
|
299
|
+
readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface ValidatedTypenameInfo {
|
|
303
|
+
readonly typeName: string;
|
|
304
|
+
readonly fieldName: TypenameFieldName;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
interface ExtractTypenameFromNamedTypeParams {
|
|
308
|
+
readonly memberType: TSTypeReference;
|
|
309
|
+
readonly typeMap: ReadonlyMap<string, ExtractedTypeInfo>;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Extract typename info from a named type by looking up its fields in the typeMap.
|
|
314
|
+
* Returns null if the type doesn't have a valid __typename or $typeName field.
|
|
315
|
+
*/
|
|
316
|
+
function extractTypenameFromNamedType(
|
|
317
|
+
params: ExtractTypenameFromNamedTypeParams,
|
|
318
|
+
): ValidatedTypenameInfo | null {
|
|
319
|
+
const { memberType, typeMap } = params;
|
|
320
|
+
|
|
321
|
+
if (memberType.kind !== "reference" || memberType.name === null) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const referencedType = typeMap.get(memberType.name);
|
|
326
|
+
if (!referencedType) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const found = findTypenameProperty(referencedType.fields, (f) => f.name);
|
|
331
|
+
if (!found) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const { property: field, fieldName } = found;
|
|
336
|
+
const { tsType } = field;
|
|
337
|
+
|
|
338
|
+
if (
|
|
339
|
+
field.optional ||
|
|
340
|
+
tsType.nullable ||
|
|
341
|
+
tsType.kind !== "literal" ||
|
|
342
|
+
tsType.name === null
|
|
343
|
+
) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { typeName: tsType.name, fieldName };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface ValidateUnionMemberTypenamesResult {
|
|
351
|
+
readonly valid: boolean;
|
|
352
|
+
readonly diagnostics: ReadonlyArray<Diagnostic>;
|
|
353
|
+
readonly memberTypenames: ReadonlyMap<number, ValidatedTypenameInfo>;
|
|
354
|
+
readonly allMembersHaveTypename: boolean;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Validates __typename or $typeName property on inline union members.
|
|
359
|
+
* Returns extracted typename values for valid members.
|
|
360
|
+
*
|
|
361
|
+
* Behavior:
|
|
362
|
+
* - For named types (needsAutoGeneration: false), extracts typename from typeMap
|
|
363
|
+
* - For inline types (needsAutoGeneration: true), validates and extracts typename
|
|
364
|
+
* - Only called for payload unions (context.kind === "resolverPayload")
|
|
365
|
+
* - __typename takes priority over $typeName if both are present
|
|
366
|
+
*
|
|
367
|
+
* Reports:
|
|
368
|
+
* - MISSING_TYPENAME_PROPERTY when neither __typename nor $typeName is present
|
|
369
|
+
* - INVALID_TYPENAME_TYPE when the property is not a string literal
|
|
370
|
+
* - OPTIONAL_TYPENAME_PROPERTY when the property is declared as optional
|
|
371
|
+
* - NULLABLE_TYPENAME_PROPERTY when the property is nullable
|
|
372
|
+
*/
|
|
373
|
+
export function validateUnionMemberTypenames(
|
|
374
|
+
params: ValidateUnionMemberTypenamesParams,
|
|
375
|
+
): ValidateUnionMemberTypenamesResult {
|
|
376
|
+
const { members, unionTypeName, sourceLocation, typeMap } = params;
|
|
377
|
+
const diagnostics: Diagnostic[] = [];
|
|
378
|
+
const memberTypenames = new Map<number, ValidatedTypenameInfo>();
|
|
379
|
+
|
|
380
|
+
// Counters for determining allMembersHaveTypename
|
|
381
|
+
let inlineTypeCount = 0;
|
|
382
|
+
let inlineTypesWithTypename = 0;
|
|
383
|
+
let namedTypeCount = 0;
|
|
384
|
+
let namedTypesWithTypename = 0;
|
|
385
|
+
|
|
386
|
+
for (let i = 0; i < members.length; i++) {
|
|
387
|
+
const member = members[i]!;
|
|
388
|
+
const memberType = member.memberType;
|
|
389
|
+
|
|
390
|
+
if (!member.needsAutoGeneration) {
|
|
391
|
+
namedTypeCount++;
|
|
392
|
+
const typenameInfo = extractTypenameFromNamedType({
|
|
393
|
+
memberType,
|
|
394
|
+
typeMap,
|
|
395
|
+
});
|
|
396
|
+
if (typenameInfo) {
|
|
397
|
+
memberTypenames.set(i, typenameInfo);
|
|
398
|
+
namedTypesWithTypename++;
|
|
399
|
+
}
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (
|
|
404
|
+
memberType.kind !== "inlineObject" ||
|
|
405
|
+
!memberType.inlineObjectProperties
|
|
406
|
+
) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
inlineTypeCount++;
|
|
411
|
+
|
|
412
|
+
const found = findTypenameProperty(
|
|
413
|
+
memberType.inlineObjectProperties,
|
|
414
|
+
(prop) => prop.name,
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
if (!found) {
|
|
418
|
+
diagnostics.push({
|
|
419
|
+
code: "MISSING_TYPENAME_PROPERTY",
|
|
420
|
+
message: `Union '${unionTypeName}' member at index ${i} is missing '__typename' or '$typeName' property. Inline union members must have a '__typename' or '$typeName' property with a string literal type.`,
|
|
421
|
+
severity: "error",
|
|
422
|
+
location: sourceLocation,
|
|
423
|
+
});
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const { property: selectedProperty, fieldName: selectedFieldName } = found;
|
|
428
|
+
const typenameType = selectedProperty.tsType;
|
|
429
|
+
|
|
430
|
+
if (selectedProperty.optional) {
|
|
431
|
+
diagnostics.push({
|
|
432
|
+
code: "OPTIONAL_TYPENAME_PROPERTY",
|
|
433
|
+
message: `Union '${unionTypeName}' member at index ${i} has optional '${selectedFieldName}' property. The '${selectedFieldName}' property must be required for union type resolution.`,
|
|
434
|
+
severity: "error",
|
|
435
|
+
location: sourceLocation,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (typenameType.nullable) {
|
|
440
|
+
diagnostics.push({
|
|
441
|
+
code: "NULLABLE_TYPENAME_PROPERTY",
|
|
442
|
+
message: `Union '${unionTypeName}' member at index ${i} has nullable '${selectedFieldName}' property. The '${selectedFieldName}' property must not be nullable for union type resolution.`,
|
|
443
|
+
severity: "error",
|
|
444
|
+
location: sourceLocation,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (typenameType.kind !== "literal" || typenameType.name === null) {
|
|
449
|
+
diagnostics.push({
|
|
450
|
+
code: "INVALID_TYPENAME_TYPE",
|
|
451
|
+
message: `Union '${unionTypeName}' member at index ${i} has '${selectedFieldName}' that is not a string literal type. Expected a string literal like '${selectedFieldName}: "TypeName"'.`,
|
|
452
|
+
severity: "error",
|
|
453
|
+
location: sourceLocation,
|
|
454
|
+
});
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (!selectedProperty.optional && !typenameType.nullable) {
|
|
459
|
+
memberTypenames.set(i, {
|
|
460
|
+
typeName: typenameType.name,
|
|
461
|
+
fieldName: selectedFieldName,
|
|
462
|
+
});
|
|
463
|
+
inlineTypesWithTypename++;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Determine allMembersHaveTypename: all members (both inline and named) must have typename
|
|
468
|
+
// for resolveType to be auto-generated. This ensures the generated resolveType function
|
|
469
|
+
// can resolve all union members correctly at runtime.
|
|
470
|
+
const totalMemberCount = inlineTypeCount + namedTypeCount;
|
|
471
|
+
const totalMembersWithTypename =
|
|
472
|
+
inlineTypesWithTypename + namedTypesWithTypename;
|
|
473
|
+
const allMembersHaveTypename =
|
|
474
|
+
totalMemberCount > 0 && totalMembersWithTypename === totalMemberCount;
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
valid: diagnostics.length === 0,
|
|
478
|
+
diagnostics,
|
|
479
|
+
memberTypenames,
|
|
480
|
+
allMembersHaveTypename,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
export type AutoTypeNameContext =
|
|
5
5
|
| ObjectFieldContext
|
|
6
6
|
| InputFieldContext
|
|
7
|
-
| ResolverArgContext
|
|
7
|
+
| ResolverArgContext
|
|
8
|
+
| ResolverPayloadContext;
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Context for Object type field inline objects.
|
|
@@ -40,6 +41,20 @@ export interface ResolverArgContext {
|
|
|
40
41
|
readonly fieldPath: ReadonlyArray<string>;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Context for resolver payload inline types.
|
|
46
|
+
* Query/Mutation: {PascalCaseFieldName}Payload
|
|
47
|
+
* Field resolver: {ParentTypeName}{PascalCaseFieldName}Payload
|
|
48
|
+
* Nested: {PayloadTypeName}{PascalCaseFieldPath} (no Input suffix)
|
|
49
|
+
*/
|
|
50
|
+
export interface ResolverPayloadContext {
|
|
51
|
+
readonly kind: "resolverPayload";
|
|
52
|
+
readonly resolverType: "query" | "mutation" | "field";
|
|
53
|
+
readonly fieldName: string;
|
|
54
|
+
readonly parentTypeName: string | null;
|
|
55
|
+
readonly fieldPath: ReadonlyArray<string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
/**
|
|
44
59
|
* Convert a string to PascalCase.
|
|
45
60
|
* Handles camelCase, snake_case, and kebab-case inputs.
|
|
@@ -71,6 +86,13 @@ function removeInputSuffix(typeName: string): string {
|
|
|
71
86
|
return typeName;
|
|
72
87
|
}
|
|
73
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Check if a type name follows the Input type naming convention.
|
|
91
|
+
*/
|
|
92
|
+
export function isInputTypeName(name: string): boolean {
|
|
93
|
+
return name.endsWith("Input");
|
|
94
|
+
}
|
|
95
|
+
|
|
74
96
|
/**
|
|
75
97
|
* Build a field context (object or input) based on the parent type name.
|
|
76
98
|
*/
|
|
@@ -95,6 +117,8 @@ export function generateAutoTypeName(context: AutoTypeNameContext): string {
|
|
|
95
117
|
return generateInputFieldTypeName(context);
|
|
96
118
|
case "resolverArg":
|
|
97
119
|
return generateResolverArgTypeName(context);
|
|
120
|
+
case "resolverPayload":
|
|
121
|
+
return generateResolverPayloadTypeName(context);
|
|
98
122
|
}
|
|
99
123
|
}
|
|
100
124
|
|
|
@@ -124,3 +148,16 @@ function generateResolverArgTypeName(context: ResolverArgContext): string {
|
|
|
124
148
|
|
|
125
149
|
return `${fieldNamePascal}${argNamePascal}${pathParts}Input`;
|
|
126
150
|
}
|
|
151
|
+
|
|
152
|
+
function generateResolverPayloadTypeName(
|
|
153
|
+
context: ResolverPayloadContext,
|
|
154
|
+
): string {
|
|
155
|
+
const fieldNamePascal = toPascalCase(context.fieldName);
|
|
156
|
+
const pathParts = context.fieldPath.map(toPascalCase).join("");
|
|
157
|
+
|
|
158
|
+
if (context.resolverType === "field" && context.parentTypeName) {
|
|
159
|
+
return `${context.parentTypeName}${fieldNamePascal}Payload${pathParts}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return `${fieldNamePascal}Payload${pathParts}`;
|
|
163
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TypenameFieldNameSet } from "./typename-types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pattern describing which typename fields are used for resolving types.
|
|
5
|
+
* - Single field: Set contains one field name (e.g., {"__typename"} or {"$typeName"})
|
|
6
|
+
* - Mixed: Set contains multiple field names (e.g., {"__typename", "$typeName"})
|
|
7
|
+
*/
|
|
8
|
+
export interface ResolveTypeFieldPattern {
|
|
9
|
+
readonly usedFieldNames: TypenameFieldNameSet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Information about an auto-generated __resolveType function for a Union type.
|
|
14
|
+
* The generated function returns `obj.__typename` to resolve the concrete type.
|
|
15
|
+
*/
|
|
16
|
+
export interface AutoGeneratedResolveType {
|
|
17
|
+
/** The name of the Union type */
|
|
18
|
+
readonly unionTypeName: string;
|
|
19
|
+
/** The field pattern used to resolve the type name */
|
|
20
|
+
readonly fieldPattern: ResolveTypeFieldPattern;
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtractResolversResult,
|
|
3
|
+
GraphQLFieldDefinition,
|
|
4
|
+
} from "../resolver-extractor/index.js";
|
|
5
|
+
|
|
6
|
+
export type ResolverType = "query" | "mutation" | "field";
|
|
7
|
+
|
|
8
|
+
export interface ResolverFieldInfo {
|
|
9
|
+
readonly field: GraphQLFieldDefinition;
|
|
10
|
+
readonly resolverType: ResolverType;
|
|
11
|
+
readonly parentTypeName: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Iterates over all resolver fields (query, mutation, field extensions) in a consistent manner.
|
|
16
|
+
* This eliminates the repeated iteration pattern across multiple collector functions.
|
|
17
|
+
*/
|
|
18
|
+
export function forEachResolverField(
|
|
19
|
+
resolversResult: ExtractResolversResult,
|
|
20
|
+
visitor: (info: ResolverFieldInfo) => void,
|
|
21
|
+
): void {
|
|
22
|
+
for (const field of resolversResult.queryFields.fields) {
|
|
23
|
+
visitor({ field, resolverType: "query", parentTypeName: null });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const field of resolversResult.mutationFields.fields) {
|
|
27
|
+
visitor({ field, resolverType: "mutation", parentTypeName: null });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const ext of resolversResult.typeExtensions) {
|
|
31
|
+
for (const field of ext.fields) {
|
|
32
|
+
visitor({
|
|
33
|
+
field,
|
|
34
|
+
resolverType: "field",
|
|
35
|
+
parentTypeName: ext.targetTypeName,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|