@constructive-io/graphql-codegen 2.20.1 → 2.22.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/README.md +15 -3
- package/cli/codegen/barrel.d.ts +4 -1
- package/cli/codegen/barrel.js +18 -12
- package/cli/codegen/client.js +33 -0
- package/cli/codegen/custom-mutations.d.ts +11 -1
- package/cli/codegen/custom-mutations.js +49 -15
- package/cli/codegen/custom-queries.d.ts +8 -0
- package/cli/codegen/custom-queries.js +82 -47
- package/cli/codegen/gql-ast.js +9 -5
- package/cli/codegen/index.js +39 -8
- package/cli/codegen/mutations.d.ts +14 -4
- package/cli/codegen/mutations.js +114 -28
- package/cli/codegen/orm/barrel.js +4 -2
- package/cli/codegen/orm/index.js +17 -0
- package/cli/codegen/orm/input-types-generator.js +83 -29
- package/cli/codegen/orm/model-generator.js +6 -4
- package/cli/codegen/queries.d.ts +7 -3
- package/cli/codegen/queries.js +185 -158
- package/cli/codegen/scalars.d.ts +6 -4
- package/cli/codegen/scalars.js +17 -9
- package/cli/codegen/schema-types-generator.d.ts +26 -0
- package/cli/codegen/schema-types-generator.js +365 -0
- package/cli/codegen/ts-ast.d.ts +3 -1
- package/cli/codegen/ts-ast.js +2 -2
- package/cli/codegen/type-resolver.d.ts +52 -6
- package/cli/codegen/type-resolver.js +97 -19
- package/cli/codegen/types.d.ts +7 -4
- package/cli/codegen/types.js +94 -41
- package/cli/codegen/utils.d.ts +20 -2
- package/cli/codegen/utils.js +32 -7
- package/cli/commands/generate-orm.js +5 -5
- package/cli/commands/generate.d.ts +4 -1
- package/cli/commands/generate.js +27 -8
- package/cli/introspect/transform-schema.d.ts +33 -21
- package/cli/introspect/transform-schema.js +31 -21
- package/esm/cli/codegen/barrel.d.ts +4 -1
- package/esm/cli/codegen/barrel.js +18 -12
- package/esm/cli/codegen/client.js +33 -0
- package/esm/cli/codegen/custom-mutations.d.ts +11 -1
- package/esm/cli/codegen/custom-mutations.js +50 -16
- package/esm/cli/codegen/custom-queries.d.ts +8 -0
- package/esm/cli/codegen/custom-queries.js +83 -48
- package/esm/cli/codegen/gql-ast.js +10 -6
- package/esm/cli/codegen/index.js +39 -8
- package/esm/cli/codegen/mutations.d.ts +14 -4
- package/esm/cli/codegen/mutations.js +115 -29
- package/esm/cli/codegen/orm/barrel.js +4 -2
- package/esm/cli/codegen/orm/index.js +17 -0
- package/esm/cli/codegen/orm/input-types-generator.js +83 -29
- package/esm/cli/codegen/orm/model-generator.js +7 -5
- package/esm/cli/codegen/queries.d.ts +7 -3
- package/esm/cli/codegen/queries.js +186 -159
- package/esm/cli/codegen/scalars.d.ts +6 -4
- package/esm/cli/codegen/scalars.js +16 -8
- package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
- package/esm/cli/codegen/schema-types-generator.js +362 -0
- package/esm/cli/codegen/ts-ast.d.ts +3 -1
- package/esm/cli/codegen/ts-ast.js +2 -2
- package/esm/cli/codegen/type-resolver.d.ts +52 -6
- package/esm/cli/codegen/type-resolver.js +97 -20
- package/esm/cli/codegen/types.d.ts +7 -4
- package/esm/cli/codegen/types.js +95 -41
- package/esm/cli/codegen/utils.d.ts +20 -2
- package/esm/cli/codegen/utils.js +31 -7
- package/esm/cli/commands/generate-orm.js +5 -5
- package/esm/cli/commands/generate.d.ts +4 -1
- package/esm/cli/commands/generate.js +27 -8
- package/esm/cli/introspect/transform-schema.d.ts +33 -21
- package/esm/cli/introspect/transform-schema.js +31 -21
- package/esm/types/config.d.ts +16 -1
- package/esm/types/config.js +6 -0
- package/esm/types/schema.d.ts +2 -0
- package/package.json +8 -6
- package/types/config.d.ts +16 -1
- package/types/config.js +6 -0
- package/types/schema.d.ts +2 -0
- package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
- package/__tests__/codegen/input-types-generator.test.js +0 -635
- package/cli/codegen/filters.d.ts +0 -27
- package/cli/codegen/filters.js +0 -357
- package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
- package/cli/codegen/orm/input-types-generator.test.js +0 -75
- package/cli/codegen/orm/select-types.test.d.ts +0 -11
- package/cli/codegen/orm/select-types.test.js +0 -22
- package/cli/introspect/transform-schema.test.d.ts +0 -1
- package/cli/introspect/transform-schema.test.js +0 -67
- package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
- package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
- package/esm/cli/codegen/filters.d.ts +0 -27
- package/esm/cli/codegen/filters.js +0 -351
- package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
- package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
- package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
- package/esm/cli/codegen/orm/select-types.test.js +0 -21
- package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
- package/esm/cli/introspect/transform-schema.test.js +0 -65
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { createProject, createSourceFile, getMinimalFormattedOutput, createFileHeader, createInterface, createTypeAlias, addSectionComment, } from './ts-ast';
|
|
2
|
+
import { getTypeBaseName } from './type-resolver';
|
|
3
|
+
import { scalarToTsType, SCALAR_NAMES, BASE_FILTER_TYPE_NAMES } from './scalars';
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Constants
|
|
6
|
+
// ============================================================================
|
|
7
|
+
/**
|
|
8
|
+
* Types that should not be generated (scalars, built-ins, types generated elsewhere)
|
|
9
|
+
*/
|
|
10
|
+
const SKIP_TYPES = new Set([
|
|
11
|
+
...SCALAR_NAMES,
|
|
12
|
+
// GraphQL built-ins
|
|
13
|
+
'Query',
|
|
14
|
+
'Mutation',
|
|
15
|
+
'Subscription',
|
|
16
|
+
'__Schema',
|
|
17
|
+
'__Type',
|
|
18
|
+
'__Field',
|
|
19
|
+
'__InputValue',
|
|
20
|
+
'__EnumValue',
|
|
21
|
+
'__Directive',
|
|
22
|
+
// Note: PageInfo and Cursor are NOT skipped - they're needed by Connection types
|
|
23
|
+
// Base filter types (generated in types.ts via filters.ts)
|
|
24
|
+
...BASE_FILTER_TYPE_NAMES,
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Type name patterns to skip (regex patterns)
|
|
28
|
+
*
|
|
29
|
+
* Note: We intentionally DO NOT skip Connection, Edge, Filter, or Patch types
|
|
30
|
+
* because they may be referenced by custom operations or payload types.
|
|
31
|
+
* Only skip Condition and OrderBy which are typically not needed.
|
|
32
|
+
*/
|
|
33
|
+
const SKIP_TYPE_PATTERNS = [
|
|
34
|
+
/Condition$/, // e.g., UserCondition (filter conditions are separate)
|
|
35
|
+
/OrderBy$/, // e.g., UsersOrderBy (ordering is handled separately)
|
|
36
|
+
];
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Type Conversion Utilities
|
|
39
|
+
// ============================================================================
|
|
40
|
+
/**
|
|
41
|
+
* Convert a CleanTypeRef to TypeScript type string
|
|
42
|
+
*/
|
|
43
|
+
function typeRefToTs(typeRef) {
|
|
44
|
+
if (typeRef.kind === 'NON_NULL') {
|
|
45
|
+
if (typeRef.ofType) {
|
|
46
|
+
return typeRefToTs(typeRef.ofType);
|
|
47
|
+
}
|
|
48
|
+
return typeRef.name ?? 'unknown';
|
|
49
|
+
}
|
|
50
|
+
if (typeRef.kind === 'LIST') {
|
|
51
|
+
if (typeRef.ofType) {
|
|
52
|
+
return `${typeRefToTs(typeRef.ofType)}[]`;
|
|
53
|
+
}
|
|
54
|
+
return 'unknown[]';
|
|
55
|
+
}
|
|
56
|
+
// Scalar or named type
|
|
57
|
+
const name = typeRef.name ?? 'unknown';
|
|
58
|
+
return scalarToTsType(name, { unknownScalar: 'name' });
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if a type is required (NON_NULL)
|
|
62
|
+
*/
|
|
63
|
+
function isRequired(typeRef) {
|
|
64
|
+
return typeRef.kind === 'NON_NULL';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if a type should be skipped
|
|
68
|
+
*/
|
|
69
|
+
function shouldSkipType(typeName, tableTypeNames) {
|
|
70
|
+
// Skip scalars and built-ins
|
|
71
|
+
if (SKIP_TYPES.has(typeName))
|
|
72
|
+
return true;
|
|
73
|
+
// Skip table entity types (already in types.ts)
|
|
74
|
+
if (tableTypeNames.has(typeName))
|
|
75
|
+
return true;
|
|
76
|
+
// Skip types matching patterns
|
|
77
|
+
for (const pattern of SKIP_TYPE_PATTERNS) {
|
|
78
|
+
if (pattern.test(typeName))
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
// Skip table-specific types that would conflict with inline types in table-based hooks.
|
|
82
|
+
// Note: Patch and CreateInput are now NOT exported from hooks (isExported: false),
|
|
83
|
+
// so we only skip Filter types here.
|
|
84
|
+
// The Filter and OrderBy types are generated inline (non-exported) by table query hooks,
|
|
85
|
+
// but schema-types should still generate them for custom operations that need them.
|
|
86
|
+
// Actually, we don't skip any table-based types now since hooks don't export them.
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// ENUM Types Generator
|
|
91
|
+
// ============================================================================
|
|
92
|
+
/**
|
|
93
|
+
* Add ENUM types to source file
|
|
94
|
+
*/
|
|
95
|
+
function addEnumTypes(sourceFile, typeRegistry, tableTypeNames) {
|
|
96
|
+
const generatedTypes = new Set();
|
|
97
|
+
addSectionComment(sourceFile, 'Enum Types');
|
|
98
|
+
for (const [typeName, typeInfo] of typeRegistry) {
|
|
99
|
+
if (typeInfo.kind !== 'ENUM')
|
|
100
|
+
continue;
|
|
101
|
+
if (shouldSkipType(typeName, tableTypeNames))
|
|
102
|
+
continue;
|
|
103
|
+
if (!typeInfo.enumValues || typeInfo.enumValues.length === 0)
|
|
104
|
+
continue;
|
|
105
|
+
const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
|
|
106
|
+
sourceFile.addTypeAlias(createTypeAlias(typeName, values));
|
|
107
|
+
generatedTypes.add(typeName);
|
|
108
|
+
}
|
|
109
|
+
return generatedTypes;
|
|
110
|
+
}
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// INPUT_OBJECT Types Generator
|
|
113
|
+
// ============================================================================
|
|
114
|
+
/**
|
|
115
|
+
* Add INPUT_OBJECT types to source file
|
|
116
|
+
* Uses iteration to handle nested input types
|
|
117
|
+
*/
|
|
118
|
+
function addInputObjectTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
|
|
119
|
+
const generatedTypes = new Set(alreadyGenerated);
|
|
120
|
+
const typesToGenerate = new Set();
|
|
121
|
+
// Collect all INPUT_OBJECT types
|
|
122
|
+
for (const [typeName, typeInfo] of typeRegistry) {
|
|
123
|
+
if (typeInfo.kind !== 'INPUT_OBJECT')
|
|
124
|
+
continue;
|
|
125
|
+
if (shouldSkipType(typeName, tableTypeNames))
|
|
126
|
+
continue;
|
|
127
|
+
if (generatedTypes.has(typeName))
|
|
128
|
+
continue;
|
|
129
|
+
typesToGenerate.add(typeName);
|
|
130
|
+
}
|
|
131
|
+
if (typesToGenerate.size === 0)
|
|
132
|
+
return generatedTypes;
|
|
133
|
+
addSectionComment(sourceFile, 'Input Object Types');
|
|
134
|
+
// Process all types - no artificial limit
|
|
135
|
+
while (typesToGenerate.size > 0) {
|
|
136
|
+
const typeNameResult = typesToGenerate.values().next();
|
|
137
|
+
if (typeNameResult.done)
|
|
138
|
+
break;
|
|
139
|
+
const typeName = typeNameResult.value;
|
|
140
|
+
typesToGenerate.delete(typeName);
|
|
141
|
+
if (generatedTypes.has(typeName))
|
|
142
|
+
continue;
|
|
143
|
+
const typeInfo = typeRegistry.get(typeName);
|
|
144
|
+
if (!typeInfo || typeInfo.kind !== 'INPUT_OBJECT')
|
|
145
|
+
continue;
|
|
146
|
+
generatedTypes.add(typeName);
|
|
147
|
+
if (typeInfo.inputFields && typeInfo.inputFields.length > 0) {
|
|
148
|
+
const properties = [];
|
|
149
|
+
for (const field of typeInfo.inputFields) {
|
|
150
|
+
const optional = !isRequired(field.type);
|
|
151
|
+
const tsType = typeRefToTs(field.type);
|
|
152
|
+
properties.push({
|
|
153
|
+
name: field.name,
|
|
154
|
+
type: tsType,
|
|
155
|
+
optional,
|
|
156
|
+
docs: field.description ? [field.description] : undefined,
|
|
157
|
+
});
|
|
158
|
+
// Follow nested Input types
|
|
159
|
+
const baseType = getTypeBaseName(field.type);
|
|
160
|
+
if (baseType &&
|
|
161
|
+
!generatedTypes.has(baseType) &&
|
|
162
|
+
!shouldSkipType(baseType, tableTypeNames)) {
|
|
163
|
+
const nestedType = typeRegistry.get(baseType);
|
|
164
|
+
if (nestedType?.kind === 'INPUT_OBJECT') {
|
|
165
|
+
typesToGenerate.add(baseType);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
sourceFile.addInterface(createInterface(typeName, properties));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// Empty input object
|
|
173
|
+
sourceFile.addInterface(createInterface(typeName, []));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return generatedTypes;
|
|
177
|
+
}
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// UNION Types Generator
|
|
180
|
+
// ============================================================================
|
|
181
|
+
/**
|
|
182
|
+
* Add UNION types to source file
|
|
183
|
+
*/
|
|
184
|
+
function addUnionTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
|
|
185
|
+
const generatedTypes = new Set(alreadyGenerated);
|
|
186
|
+
const unionTypesToGenerate = new Set();
|
|
187
|
+
// Collect all UNION types
|
|
188
|
+
for (const [typeName, typeInfo] of typeRegistry) {
|
|
189
|
+
if (typeInfo.kind !== 'UNION')
|
|
190
|
+
continue;
|
|
191
|
+
if (shouldSkipType(typeName, tableTypeNames))
|
|
192
|
+
continue;
|
|
193
|
+
if (generatedTypes.has(typeName))
|
|
194
|
+
continue;
|
|
195
|
+
unionTypesToGenerate.add(typeName);
|
|
196
|
+
}
|
|
197
|
+
if (unionTypesToGenerate.size === 0)
|
|
198
|
+
return generatedTypes;
|
|
199
|
+
addSectionComment(sourceFile, 'Union Types');
|
|
200
|
+
for (const typeName of unionTypesToGenerate) {
|
|
201
|
+
const typeInfo = typeRegistry.get(typeName);
|
|
202
|
+
if (!typeInfo || typeInfo.kind !== 'UNION')
|
|
203
|
+
continue;
|
|
204
|
+
if (!typeInfo.possibleTypes || typeInfo.possibleTypes.length === 0)
|
|
205
|
+
continue;
|
|
206
|
+
// Generate union type as TypeScript union
|
|
207
|
+
const unionMembers = typeInfo.possibleTypes.join(' | ');
|
|
208
|
+
sourceFile.addTypeAlias(createTypeAlias(typeName, unionMembers));
|
|
209
|
+
generatedTypes.add(typeName);
|
|
210
|
+
}
|
|
211
|
+
return generatedTypes;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Collect return types from Query and Mutation root types
|
|
215
|
+
* This dynamically discovers what OBJECT types need to be generated
|
|
216
|
+
* based on actual schema structure, not pattern matching
|
|
217
|
+
*/
|
|
218
|
+
function collectReturnTypesFromRootTypes(typeRegistry, tableTypeNames) {
|
|
219
|
+
const returnTypes = new Set();
|
|
220
|
+
// Get Query and Mutation root types
|
|
221
|
+
const queryType = typeRegistry.get('Query');
|
|
222
|
+
const mutationType = typeRegistry.get('Mutation');
|
|
223
|
+
const processFields = (fields) => {
|
|
224
|
+
if (!fields)
|
|
225
|
+
return;
|
|
226
|
+
for (const field of fields) {
|
|
227
|
+
const baseType = getTypeBaseName(field.type);
|
|
228
|
+
if (baseType && !shouldSkipType(baseType, tableTypeNames)) {
|
|
229
|
+
const typeInfo = typeRegistry.get(baseType);
|
|
230
|
+
if (typeInfo?.kind === 'OBJECT') {
|
|
231
|
+
returnTypes.add(baseType);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
if (queryType?.fields)
|
|
237
|
+
processFields(queryType.fields);
|
|
238
|
+
if (mutationType?.fields)
|
|
239
|
+
processFields(mutationType.fields);
|
|
240
|
+
return returnTypes;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Add Payload OBJECT types to source file
|
|
244
|
+
* These are return types from mutations (e.g., LoginPayload, BootstrapUserPayload)
|
|
245
|
+
*
|
|
246
|
+
* Also tracks which table entity types are referenced so they can be imported.
|
|
247
|
+
*
|
|
248
|
+
* Uses dynamic type discovery from Query/Mutation return types instead of pattern matching.
|
|
249
|
+
*/
|
|
250
|
+
function addPayloadObjectTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
|
|
251
|
+
const generatedTypes = new Set(alreadyGenerated);
|
|
252
|
+
const referencedTableTypes = new Set();
|
|
253
|
+
// Dynamically collect return types from Query and Mutation
|
|
254
|
+
const typesToGenerate = collectReturnTypesFromRootTypes(typeRegistry, tableTypeNames);
|
|
255
|
+
// Filter out already generated types
|
|
256
|
+
for (const typeName of Array.from(typesToGenerate)) {
|
|
257
|
+
if (generatedTypes.has(typeName)) {
|
|
258
|
+
typesToGenerate.delete(typeName);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (typesToGenerate.size === 0) {
|
|
262
|
+
return { generatedTypes, referencedTableTypes };
|
|
263
|
+
}
|
|
264
|
+
addSectionComment(sourceFile, 'Payload/Return Object Types');
|
|
265
|
+
// Process all types - no artificial limit
|
|
266
|
+
while (typesToGenerate.size > 0) {
|
|
267
|
+
const typeNameResult = typesToGenerate.values().next();
|
|
268
|
+
if (typeNameResult.done)
|
|
269
|
+
break;
|
|
270
|
+
const typeName = typeNameResult.value;
|
|
271
|
+
typesToGenerate.delete(typeName);
|
|
272
|
+
if (generatedTypes.has(typeName))
|
|
273
|
+
continue;
|
|
274
|
+
const typeInfo = typeRegistry.get(typeName);
|
|
275
|
+
if (!typeInfo || typeInfo.kind !== 'OBJECT')
|
|
276
|
+
continue;
|
|
277
|
+
generatedTypes.add(typeName);
|
|
278
|
+
if (typeInfo.fields && typeInfo.fields.length > 0) {
|
|
279
|
+
const properties = [];
|
|
280
|
+
for (const field of typeInfo.fields) {
|
|
281
|
+
const baseType = getTypeBaseName(field.type);
|
|
282
|
+
// Skip Query and Mutation fields
|
|
283
|
+
if (baseType === 'Query' || baseType === 'Mutation')
|
|
284
|
+
continue;
|
|
285
|
+
const tsType = typeRefToTs(field.type);
|
|
286
|
+
const isNullable = !isRequired(field.type);
|
|
287
|
+
properties.push({
|
|
288
|
+
name: field.name,
|
|
289
|
+
type: isNullable ? `${tsType} | null` : tsType,
|
|
290
|
+
optional: isNullable,
|
|
291
|
+
docs: field.description ? [field.description] : undefined,
|
|
292
|
+
});
|
|
293
|
+
// Track table entity types that are referenced
|
|
294
|
+
if (baseType && tableTypeNames.has(baseType)) {
|
|
295
|
+
referencedTableTypes.add(baseType);
|
|
296
|
+
}
|
|
297
|
+
// Follow nested OBJECT types that aren't table types
|
|
298
|
+
if (baseType &&
|
|
299
|
+
!generatedTypes.has(baseType) &&
|
|
300
|
+
!shouldSkipType(baseType, tableTypeNames)) {
|
|
301
|
+
const nestedType = typeRegistry.get(baseType);
|
|
302
|
+
if (nestedType?.kind === 'OBJECT') {
|
|
303
|
+
typesToGenerate.add(baseType);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
sourceFile.addInterface(createInterface(typeName, properties));
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
// Empty payload object
|
|
311
|
+
sourceFile.addInterface(createInterface(typeName, []));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return { generatedTypes, referencedTableTypes };
|
|
315
|
+
}
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// Main Generator
|
|
318
|
+
// ============================================================================
|
|
319
|
+
/**
|
|
320
|
+
* Generate comprehensive schema-types.ts file using ts-morph AST
|
|
321
|
+
*
|
|
322
|
+
* This generates all Input/Payload/Enum types from the TypeRegistry
|
|
323
|
+
* that are needed by custom mutation/query hooks.
|
|
324
|
+
*/
|
|
325
|
+
export function generateSchemaTypesFile(options) {
|
|
326
|
+
const { typeRegistry, tableTypeNames } = options;
|
|
327
|
+
const project = createProject();
|
|
328
|
+
const sourceFile = createSourceFile(project, 'schema-types.ts');
|
|
329
|
+
// Add file header
|
|
330
|
+
sourceFile.insertText(0, createFileHeader('GraphQL schema types for custom operations') + '\n');
|
|
331
|
+
// Track all generated types
|
|
332
|
+
let generatedTypes = new Set();
|
|
333
|
+
// 1. Generate ENUM types
|
|
334
|
+
const enumTypes = addEnumTypes(sourceFile, typeRegistry, tableTypeNames);
|
|
335
|
+
generatedTypes = new Set([...generatedTypes, ...enumTypes]);
|
|
336
|
+
// 2. Generate UNION types
|
|
337
|
+
const unionTypes = addUnionTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
|
|
338
|
+
generatedTypes = new Set([...generatedTypes, ...unionTypes]);
|
|
339
|
+
// 3. Generate INPUT_OBJECT types
|
|
340
|
+
const inputTypes = addInputObjectTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
|
|
341
|
+
generatedTypes = new Set([...generatedTypes, ...inputTypes]);
|
|
342
|
+
// 4. Generate Payload OBJECT types
|
|
343
|
+
const payloadResult = addPayloadObjectTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
|
|
344
|
+
// 5. Add imports from types.ts (table entity types + base filter types)
|
|
345
|
+
const referencedTableTypes = Array.from(payloadResult.referencedTableTypes).sort();
|
|
346
|
+
// Always import base filter types since generated Filter interfaces reference them
|
|
347
|
+
const baseFilterImports = Array.from(BASE_FILTER_TYPE_NAMES).sort();
|
|
348
|
+
const allTypesImports = [...referencedTableTypes, ...baseFilterImports];
|
|
349
|
+
if (allTypesImports.length > 0) {
|
|
350
|
+
// Insert import after the file header comment
|
|
351
|
+
const importStatement = `import type { ${allTypesImports.join(', ')} } from './types';\n\n`;
|
|
352
|
+
// Find position after header (after first */ + newlines)
|
|
353
|
+
const headerEndIndex = sourceFile.getFullText().indexOf('*/') + 3;
|
|
354
|
+
sourceFile.insertText(headerEndIndex, '\n' + importStatement);
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
fileName: 'schema-types.ts',
|
|
358
|
+
content: getMinimalFormattedOutput(sourceFile),
|
|
359
|
+
generatedEnums: Array.from(enumTypes).sort(),
|
|
360
|
+
referencedTableTypes,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
@@ -66,7 +66,9 @@ export declare function createInterface(name: string, properties: InterfacePrope
|
|
|
66
66
|
export declare function createFilterInterface(name: string, fieldFilters: Array<{
|
|
67
67
|
fieldName: string;
|
|
68
68
|
filterType: string;
|
|
69
|
-
}
|
|
69
|
+
}>, options?: {
|
|
70
|
+
isExported?: boolean;
|
|
71
|
+
}): InterfaceDeclarationStructure;
|
|
70
72
|
/**
|
|
71
73
|
* Create type alias declaration structure
|
|
72
74
|
*/
|
|
@@ -143,7 +143,7 @@ export function createInterface(name, properties, options) {
|
|
|
143
143
|
/**
|
|
144
144
|
* Create filter interface with standard PostGraphile operators
|
|
145
145
|
*/
|
|
146
|
-
export function createFilterInterface(name, fieldFilters) {
|
|
146
|
+
export function createFilterInterface(name, fieldFilters, options) {
|
|
147
147
|
const properties = [
|
|
148
148
|
...fieldFilters.map((f) => ({
|
|
149
149
|
name: f.fieldName,
|
|
@@ -154,7 +154,7 @@ export function createFilterInterface(name, fieldFilters) {
|
|
|
154
154
|
{ name: 'or', type: `${name}[]`, optional: true, docs: ['Logical OR'] },
|
|
155
155
|
{ name: 'not', type: name, optional: true, docs: ['Logical NOT'] },
|
|
156
156
|
];
|
|
157
|
-
return createInterface(name, properties);
|
|
157
|
+
return createInterface(name, properties, { isExported: options?.isExported ?? true });
|
|
158
158
|
}
|
|
159
159
|
// ============================================================================
|
|
160
160
|
// Type alias builders
|
|
@@ -6,6 +6,34 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { CleanTypeRef, CleanArgument, CleanObjectField } from '../../types/schema';
|
|
8
8
|
import type { InterfaceProperty } from './ts-ast';
|
|
9
|
+
/**
|
|
10
|
+
* Interface for tracking referenced types during code generation
|
|
11
|
+
*/
|
|
12
|
+
export interface TypeTracker {
|
|
13
|
+
/** Set of type names that have been referenced */
|
|
14
|
+
referencedTypes: Set<string>;
|
|
15
|
+
/** Track a type reference */
|
|
16
|
+
track(typeName: string): void;
|
|
17
|
+
/** Get importable types from schema-types.ts (Input/Payload/Enum types) */
|
|
18
|
+
getImportableTypes(): string[];
|
|
19
|
+
/** Get importable types from types.ts (table entity types) */
|
|
20
|
+
getTableTypes(): string[];
|
|
21
|
+
/** Reset the tracker */
|
|
22
|
+
reset(): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Options for creating a TypeTracker
|
|
26
|
+
*/
|
|
27
|
+
export interface TypeTrackerOptions {
|
|
28
|
+
/** Table entity type names that should be imported from types.ts */
|
|
29
|
+
tableTypeNames?: Set<string>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a new TypeTracker instance
|
|
33
|
+
*
|
|
34
|
+
* @param options - Optional configuration for the tracker
|
|
35
|
+
*/
|
|
36
|
+
export declare function createTypeTracker(options?: TypeTrackerOptions): TypeTracker;
|
|
9
37
|
/**
|
|
10
38
|
* Convert a GraphQL scalar type to TypeScript type
|
|
11
39
|
*/
|
|
@@ -13,13 +41,19 @@ export declare function scalarToTsType(scalarName: string): string;
|
|
|
13
41
|
/**
|
|
14
42
|
* Convert a CleanTypeRef to a TypeScript type string
|
|
15
43
|
* Handles nested LIST and NON_NULL wrappers
|
|
44
|
+
*
|
|
45
|
+
* @param typeRef - The GraphQL type reference
|
|
46
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
16
47
|
*/
|
|
17
|
-
export declare function typeRefToTsType(typeRef: CleanTypeRef): string;
|
|
48
|
+
export declare function typeRefToTsType(typeRef: CleanTypeRef, tracker?: TypeTracker): string;
|
|
18
49
|
/**
|
|
19
50
|
* Convert a CleanTypeRef to a nullable TypeScript type string
|
|
20
51
|
* (for optional fields that can be null)
|
|
52
|
+
*
|
|
53
|
+
* @param typeRef - The GraphQL type reference
|
|
54
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
21
55
|
*/
|
|
22
|
-
export declare function typeRefToNullableTsType(typeRef: CleanTypeRef): string;
|
|
56
|
+
export declare function typeRefToNullableTsType(typeRef: CleanTypeRef, tracker?: TypeTracker): string;
|
|
23
57
|
/**
|
|
24
58
|
* Check if a type reference is required (wrapped in NON_NULL)
|
|
25
59
|
*/
|
|
@@ -38,20 +72,32 @@ export declare function getTypeBaseName(typeRef: CleanTypeRef): string | null;
|
|
|
38
72
|
export declare function getBaseTypeKind(typeRef: CleanTypeRef): CleanTypeRef['kind'];
|
|
39
73
|
/**
|
|
40
74
|
* Convert CleanArgument to InterfaceProperty for ts-morph
|
|
75
|
+
*
|
|
76
|
+
* @param arg - The GraphQL argument
|
|
77
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
41
78
|
*/
|
|
42
|
-
export declare function argumentToInterfaceProperty(arg: CleanArgument): InterfaceProperty;
|
|
79
|
+
export declare function argumentToInterfaceProperty(arg: CleanArgument, tracker?: TypeTracker): InterfaceProperty;
|
|
43
80
|
/**
|
|
44
81
|
* Convert CleanObjectField to InterfaceProperty for ts-morph
|
|
82
|
+
*
|
|
83
|
+
* @param field - The GraphQL object field
|
|
84
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
45
85
|
*/
|
|
46
|
-
export declare function fieldToInterfaceProperty(field: CleanObjectField): InterfaceProperty;
|
|
86
|
+
export declare function fieldToInterfaceProperty(field: CleanObjectField, tracker?: TypeTracker): InterfaceProperty;
|
|
47
87
|
/**
|
|
48
88
|
* Convert an array of CleanArguments to InterfaceProperty array
|
|
89
|
+
*
|
|
90
|
+
* @param args - The GraphQL arguments
|
|
91
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
49
92
|
*/
|
|
50
|
-
export declare function argumentsToInterfaceProperties(args: CleanArgument[]): InterfaceProperty[];
|
|
93
|
+
export declare function argumentsToInterfaceProperties(args: CleanArgument[], tracker?: TypeTracker): InterfaceProperty[];
|
|
51
94
|
/**
|
|
52
95
|
* Convert an array of CleanObjectFields to InterfaceProperty array
|
|
96
|
+
*
|
|
97
|
+
* @param fields - The GraphQL object fields
|
|
98
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
53
99
|
*/
|
|
54
|
-
export declare function fieldsToInterfaceProperties(fields: CleanObjectField[]): InterfaceProperty[];
|
|
100
|
+
export declare function fieldsToInterfaceProperties(fields: CleanObjectField[], tracker?: TypeTracker): InterfaceProperty[];
|
|
55
101
|
/**
|
|
56
102
|
* Check if a field should be skipped in selections
|
|
57
103
|
*/
|
|
@@ -1,4 +1,57 @@
|
|
|
1
|
-
import { scalarToTsType as resolveScalarToTs } from './scalars';
|
|
1
|
+
import { scalarToTsType as resolveScalarToTs, SCALAR_NAMES } from './scalars';
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Type Tracker for Collecting Referenced Types
|
|
4
|
+
// ============================================================================
|
|
5
|
+
/**
|
|
6
|
+
* Types that should not be tracked (scalars, built-ins, internal types)
|
|
7
|
+
*/
|
|
8
|
+
const SKIP_TYPE_TRACKING = new Set([
|
|
9
|
+
...SCALAR_NAMES,
|
|
10
|
+
// GraphQL built-ins
|
|
11
|
+
'Query',
|
|
12
|
+
'Mutation',
|
|
13
|
+
'Subscription',
|
|
14
|
+
'__Schema',
|
|
15
|
+
'__Type',
|
|
16
|
+
'__Field',
|
|
17
|
+
'__InputValue',
|
|
18
|
+
'__EnumValue',
|
|
19
|
+
'__Directive',
|
|
20
|
+
// Connection types (handled separately)
|
|
21
|
+
'PageInfo',
|
|
22
|
+
]);
|
|
23
|
+
/**
|
|
24
|
+
* Create a new TypeTracker instance
|
|
25
|
+
*
|
|
26
|
+
* @param options - Optional configuration for the tracker
|
|
27
|
+
*/
|
|
28
|
+
export function createTypeTracker(options) {
|
|
29
|
+
const referencedTypes = new Set();
|
|
30
|
+
const tableTypeNames = options?.tableTypeNames ?? new Set();
|
|
31
|
+
return {
|
|
32
|
+
referencedTypes,
|
|
33
|
+
track(typeName) {
|
|
34
|
+
if (typeName && !SKIP_TYPE_TRACKING.has(typeName)) {
|
|
35
|
+
referencedTypes.add(typeName);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
getImportableTypes() {
|
|
39
|
+
// Return schema types (not table entity types)
|
|
40
|
+
return Array.from(referencedTypes)
|
|
41
|
+
.filter((name) => !tableTypeNames.has(name))
|
|
42
|
+
.sort();
|
|
43
|
+
},
|
|
44
|
+
getTableTypes() {
|
|
45
|
+
// Return table entity types only
|
|
46
|
+
return Array.from(referencedTypes)
|
|
47
|
+
.filter((name) => tableTypeNames.has(name))
|
|
48
|
+
.sort();
|
|
49
|
+
},
|
|
50
|
+
reset() {
|
|
51
|
+
referencedTypes.clear();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
2
55
|
// ============================================================================
|
|
3
56
|
// GraphQL to TypeScript Type Mapping
|
|
4
57
|
// ============================================================================
|
|
@@ -14,32 +67,41 @@ export function scalarToTsType(scalarName) {
|
|
|
14
67
|
/**
|
|
15
68
|
* Convert a CleanTypeRef to a TypeScript type string
|
|
16
69
|
* Handles nested LIST and NON_NULL wrappers
|
|
70
|
+
*
|
|
71
|
+
* @param typeRef - The GraphQL type reference
|
|
72
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
17
73
|
*/
|
|
18
|
-
export function typeRefToTsType(typeRef) {
|
|
74
|
+
export function typeRefToTsType(typeRef, tracker) {
|
|
19
75
|
switch (typeRef.kind) {
|
|
20
76
|
case 'NON_NULL':
|
|
21
77
|
// Non-null wrapper - unwrap and return the inner type
|
|
22
78
|
if (typeRef.ofType) {
|
|
23
|
-
return typeRefToTsType(typeRef.ofType);
|
|
79
|
+
return typeRefToTsType(typeRef.ofType, tracker);
|
|
24
80
|
}
|
|
25
81
|
return 'unknown';
|
|
26
82
|
case 'LIST':
|
|
27
83
|
// List wrapper - wrap inner type in array
|
|
28
84
|
if (typeRef.ofType) {
|
|
29
|
-
const innerType = typeRefToTsType(typeRef.ofType);
|
|
85
|
+
const innerType = typeRefToTsType(typeRef.ofType, tracker);
|
|
30
86
|
return `${innerType}[]`;
|
|
31
87
|
}
|
|
32
88
|
return 'unknown[]';
|
|
33
89
|
case 'SCALAR':
|
|
34
90
|
// Scalar type - map to TS type
|
|
35
91
|
return scalarToTsType(typeRef.name ?? 'unknown');
|
|
36
|
-
case 'ENUM':
|
|
37
|
-
// Enum type - use the GraphQL enum name
|
|
38
|
-
|
|
92
|
+
case 'ENUM': {
|
|
93
|
+
// Enum type - use the GraphQL enum name and track it
|
|
94
|
+
const typeName = typeRef.name ?? 'string';
|
|
95
|
+
tracker?.track(typeName);
|
|
96
|
+
return typeName;
|
|
97
|
+
}
|
|
39
98
|
case 'OBJECT':
|
|
40
|
-
case 'INPUT_OBJECT':
|
|
41
|
-
// Object types - use the GraphQL type name
|
|
42
|
-
|
|
99
|
+
case 'INPUT_OBJECT': {
|
|
100
|
+
// Object types - use the GraphQL type name and track it
|
|
101
|
+
const typeName = typeRef.name ?? 'unknown';
|
|
102
|
+
tracker?.track(typeName);
|
|
103
|
+
return typeName;
|
|
104
|
+
}
|
|
43
105
|
default:
|
|
44
106
|
return 'unknown';
|
|
45
107
|
}
|
|
@@ -47,9 +109,12 @@ export function typeRefToTsType(typeRef) {
|
|
|
47
109
|
/**
|
|
48
110
|
* Convert a CleanTypeRef to a nullable TypeScript type string
|
|
49
111
|
* (for optional fields that can be null)
|
|
112
|
+
*
|
|
113
|
+
* @param typeRef - The GraphQL type reference
|
|
114
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
50
115
|
*/
|
|
51
|
-
export function typeRefToNullableTsType(typeRef) {
|
|
52
|
-
const baseType = typeRefToTsType(typeRef);
|
|
116
|
+
export function typeRefToNullableTsType(typeRef, tracker) {
|
|
117
|
+
const baseType = typeRefToTsType(typeRef, tracker);
|
|
53
118
|
// If the outer type is NON_NULL, it's required
|
|
54
119
|
if (typeRef.kind === 'NON_NULL') {
|
|
55
120
|
return baseType;
|
|
@@ -100,37 +165,49 @@ export function getBaseTypeKind(typeRef) {
|
|
|
100
165
|
// ============================================================================
|
|
101
166
|
/**
|
|
102
167
|
* Convert CleanArgument to InterfaceProperty for ts-morph
|
|
168
|
+
*
|
|
169
|
+
* @param arg - The GraphQL argument
|
|
170
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
103
171
|
*/
|
|
104
|
-
export function argumentToInterfaceProperty(arg) {
|
|
172
|
+
export function argumentToInterfaceProperty(arg, tracker) {
|
|
105
173
|
return {
|
|
106
174
|
name: arg.name,
|
|
107
|
-
type: typeRefToTsType(arg.type),
|
|
175
|
+
type: typeRefToTsType(arg.type, tracker),
|
|
108
176
|
optional: !isTypeRequired(arg.type),
|
|
109
177
|
docs: arg.description ? [arg.description] : undefined,
|
|
110
178
|
};
|
|
111
179
|
}
|
|
112
180
|
/**
|
|
113
181
|
* Convert CleanObjectField to InterfaceProperty for ts-morph
|
|
182
|
+
*
|
|
183
|
+
* @param field - The GraphQL object field
|
|
184
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
114
185
|
*/
|
|
115
|
-
export function fieldToInterfaceProperty(field) {
|
|
186
|
+
export function fieldToInterfaceProperty(field, tracker) {
|
|
116
187
|
return {
|
|
117
188
|
name: field.name,
|
|
118
|
-
type: typeRefToNullableTsType(field.type),
|
|
189
|
+
type: typeRefToNullableTsType(field.type, tracker),
|
|
119
190
|
optional: false, // Fields are always present, just potentially null
|
|
120
191
|
docs: field.description ? [field.description] : undefined,
|
|
121
192
|
};
|
|
122
193
|
}
|
|
123
194
|
/**
|
|
124
195
|
* Convert an array of CleanArguments to InterfaceProperty array
|
|
196
|
+
*
|
|
197
|
+
* @param args - The GraphQL arguments
|
|
198
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
125
199
|
*/
|
|
126
|
-
export function argumentsToInterfaceProperties(args) {
|
|
127
|
-
return args.map(argumentToInterfaceProperty);
|
|
200
|
+
export function argumentsToInterfaceProperties(args, tracker) {
|
|
201
|
+
return args.map((arg) => argumentToInterfaceProperty(arg, tracker));
|
|
128
202
|
}
|
|
129
203
|
/**
|
|
130
204
|
* Convert an array of CleanObjectFields to InterfaceProperty array
|
|
205
|
+
*
|
|
206
|
+
* @param fields - The GraphQL object fields
|
|
207
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
131
208
|
*/
|
|
132
|
-
export function fieldsToInterfaceProperties(fields) {
|
|
133
|
-
return fields.map(fieldToInterfaceProperty);
|
|
209
|
+
export function fieldsToInterfaceProperties(fields, tracker) {
|
|
210
|
+
return fields.map((field) => fieldToInterfaceProperty(field, tracker));
|
|
134
211
|
}
|
|
135
212
|
// ============================================================================
|
|
136
213
|
// Type Filtering
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { CleanTable } from '../../types/schema';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Options for generating types.ts
|
|
7
7
|
*/
|
|
8
|
-
export
|
|
8
|
+
export interface GenerateTypesOptions {
|
|
9
|
+
/** Enum type names that are available from schema-types.ts */
|
|
10
|
+
enumsFromSchemaTypes?: string[];
|
|
11
|
+
}
|
|
9
12
|
/**
|
|
10
|
-
* Generate
|
|
13
|
+
* Generate types.ts content with all entity interfaces and base filter types
|
|
11
14
|
*/
|
|
12
|
-
export declare function
|
|
15
|
+
export declare function generateTypesFile(tables: CleanTable[], options?: GenerateTypesOptions): string;
|