@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.
Files changed (96) hide show
  1. package/README.md +15 -3
  2. package/cli/codegen/barrel.d.ts +4 -1
  3. package/cli/codegen/barrel.js +18 -12
  4. package/cli/codegen/client.js +33 -0
  5. package/cli/codegen/custom-mutations.d.ts +11 -1
  6. package/cli/codegen/custom-mutations.js +49 -15
  7. package/cli/codegen/custom-queries.d.ts +8 -0
  8. package/cli/codegen/custom-queries.js +82 -47
  9. package/cli/codegen/gql-ast.js +9 -5
  10. package/cli/codegen/index.js +39 -8
  11. package/cli/codegen/mutations.d.ts +14 -4
  12. package/cli/codegen/mutations.js +114 -28
  13. package/cli/codegen/orm/barrel.js +4 -2
  14. package/cli/codegen/orm/index.js +17 -0
  15. package/cli/codegen/orm/input-types-generator.js +83 -29
  16. package/cli/codegen/orm/model-generator.js +6 -4
  17. package/cli/codegen/queries.d.ts +7 -3
  18. package/cli/codegen/queries.js +185 -158
  19. package/cli/codegen/scalars.d.ts +6 -4
  20. package/cli/codegen/scalars.js +17 -9
  21. package/cli/codegen/schema-types-generator.d.ts +26 -0
  22. package/cli/codegen/schema-types-generator.js +365 -0
  23. package/cli/codegen/ts-ast.d.ts +3 -1
  24. package/cli/codegen/ts-ast.js +2 -2
  25. package/cli/codegen/type-resolver.d.ts +52 -6
  26. package/cli/codegen/type-resolver.js +97 -19
  27. package/cli/codegen/types.d.ts +7 -4
  28. package/cli/codegen/types.js +94 -41
  29. package/cli/codegen/utils.d.ts +20 -2
  30. package/cli/codegen/utils.js +32 -7
  31. package/cli/commands/generate-orm.js +5 -5
  32. package/cli/commands/generate.d.ts +4 -1
  33. package/cli/commands/generate.js +27 -8
  34. package/cli/introspect/transform-schema.d.ts +33 -21
  35. package/cli/introspect/transform-schema.js +31 -21
  36. package/esm/cli/codegen/barrel.d.ts +4 -1
  37. package/esm/cli/codegen/barrel.js +18 -12
  38. package/esm/cli/codegen/client.js +33 -0
  39. package/esm/cli/codegen/custom-mutations.d.ts +11 -1
  40. package/esm/cli/codegen/custom-mutations.js +50 -16
  41. package/esm/cli/codegen/custom-queries.d.ts +8 -0
  42. package/esm/cli/codegen/custom-queries.js +83 -48
  43. package/esm/cli/codegen/gql-ast.js +10 -6
  44. package/esm/cli/codegen/index.js +39 -8
  45. package/esm/cli/codegen/mutations.d.ts +14 -4
  46. package/esm/cli/codegen/mutations.js +115 -29
  47. package/esm/cli/codegen/orm/barrel.js +4 -2
  48. package/esm/cli/codegen/orm/index.js +17 -0
  49. package/esm/cli/codegen/orm/input-types-generator.js +83 -29
  50. package/esm/cli/codegen/orm/model-generator.js +7 -5
  51. package/esm/cli/codegen/queries.d.ts +7 -3
  52. package/esm/cli/codegen/queries.js +186 -159
  53. package/esm/cli/codegen/scalars.d.ts +6 -4
  54. package/esm/cli/codegen/scalars.js +16 -8
  55. package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
  56. package/esm/cli/codegen/schema-types-generator.js +362 -0
  57. package/esm/cli/codegen/ts-ast.d.ts +3 -1
  58. package/esm/cli/codegen/ts-ast.js +2 -2
  59. package/esm/cli/codegen/type-resolver.d.ts +52 -6
  60. package/esm/cli/codegen/type-resolver.js +97 -20
  61. package/esm/cli/codegen/types.d.ts +7 -4
  62. package/esm/cli/codegen/types.js +95 -41
  63. package/esm/cli/codegen/utils.d.ts +20 -2
  64. package/esm/cli/codegen/utils.js +31 -7
  65. package/esm/cli/commands/generate-orm.js +5 -5
  66. package/esm/cli/commands/generate.d.ts +4 -1
  67. package/esm/cli/commands/generate.js +27 -8
  68. package/esm/cli/introspect/transform-schema.d.ts +33 -21
  69. package/esm/cli/introspect/transform-schema.js +31 -21
  70. package/esm/types/config.d.ts +16 -1
  71. package/esm/types/config.js +6 -0
  72. package/esm/types/schema.d.ts +2 -0
  73. package/package.json +8 -6
  74. package/types/config.d.ts +16 -1
  75. package/types/config.js +6 -0
  76. package/types/schema.d.ts +2 -0
  77. package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  78. package/__tests__/codegen/input-types-generator.test.js +0 -635
  79. package/cli/codegen/filters.d.ts +0 -27
  80. package/cli/codegen/filters.js +0 -357
  81. package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  82. package/cli/codegen/orm/input-types-generator.test.js +0 -75
  83. package/cli/codegen/orm/select-types.test.d.ts +0 -11
  84. package/cli/codegen/orm/select-types.test.js +0 -22
  85. package/cli/introspect/transform-schema.test.d.ts +0 -1
  86. package/cli/introspect/transform-schema.test.js +0 -67
  87. package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  88. package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
  89. package/esm/cli/codegen/filters.d.ts +0 -27
  90. package/esm/cli/codegen/filters.js +0 -351
  91. package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  92. package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
  93. package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
  94. package/esm/cli/codegen/orm/select-types.test.js +0 -21
  95. package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
  96. 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
- }>): InterfaceDeclarationStructure;
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
- return typeRef.name ?? 'string';
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
- return typeRef.name ?? 'unknown';
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
- * Generate types.ts content with all entity interfaces and base filter types
6
+ * Options for generating types.ts
7
7
  */
8
- export declare function generateTypesFile(tables: CleanTable[]): string;
8
+ export interface GenerateTypesOptions {
9
+ /** Enum type names that are available from schema-types.ts */
10
+ enumsFromSchemaTypes?: string[];
11
+ }
9
12
  /**
10
- * Generate a minimal entity type (just id and display fields)
13
+ * Generate types.ts content with all entity interfaces and base filter types
11
14
  */
12
- export declare function generateMinimalEntityType(table: CleanTable): string;
15
+ export declare function generateTypesFile(tables: CleanTable[], options?: GenerateTypesOptions): string;