@constructive-io/graphql-codegen 2.23.3 → 2.24.1

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 (92) hide show
  1. package/README.md +147 -2
  2. package/cli/codegen/babel-ast.d.ts +53 -0
  3. package/cli/codegen/babel-ast.js +160 -0
  4. package/cli/codegen/barrel.d.ts +7 -2
  5. package/cli/codegen/barrel.js +193 -102
  6. package/cli/codegen/client.js +61 -0
  7. package/cli/codegen/custom-mutations.d.ts +2 -12
  8. package/cli/codegen/custom-mutations.js +116 -124
  9. package/cli/codegen/custom-queries.d.ts +2 -10
  10. package/cli/codegen/custom-queries.js +236 -335
  11. package/cli/codegen/gql-ast.js +22 -1
  12. package/cli/codegen/index.d.ts +3 -0
  13. package/cli/codegen/index.js +73 -3
  14. package/cli/codegen/invalidation.d.ts +20 -0
  15. package/cli/codegen/invalidation.js +327 -0
  16. package/cli/codegen/mutation-keys.d.ts +24 -0
  17. package/cli/codegen/mutation-keys.js +247 -0
  18. package/cli/codegen/mutations.d.ts +5 -19
  19. package/cli/codegen/mutations.js +385 -383
  20. package/cli/codegen/orm/barrel.d.ts +1 -1
  21. package/cli/codegen/orm/barrel.js +42 -10
  22. package/cli/codegen/orm/client-generator.d.ts +1 -19
  23. package/cli/codegen/orm/client-generator.js +108 -77
  24. package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  25. package/cli/codegen/orm/custom-ops-generator.js +192 -235
  26. package/cli/codegen/orm/input-types-generator.d.ts +13 -1
  27. package/cli/codegen/orm/input-types-generator.js +425 -147
  28. package/cli/codegen/orm/model-generator.d.ts +1 -19
  29. package/cli/codegen/orm/model-generator.js +229 -234
  30. package/cli/codegen/queries.d.ts +4 -12
  31. package/cli/codegen/queries.js +660 -390
  32. package/cli/codegen/query-keys.d.ts +15 -0
  33. package/cli/codegen/query-keys.js +477 -0
  34. package/cli/codegen/scalars.js +1 -0
  35. package/cli/codegen/schema-types-generator.d.ts +15 -10
  36. package/cli/codegen/schema-types-generator.js +87 -175
  37. package/cli/codegen/type-resolver.d.ts +1 -30
  38. package/cli/codegen/type-resolver.js +0 -53
  39. package/cli/codegen/types.d.ts +1 -1
  40. package/cli/codegen/types.js +76 -21
  41. package/cli/codegen/utils.d.ts +6 -0
  42. package/cli/codegen/utils.js +19 -0
  43. package/esm/cli/codegen/babel-ast.d.ts +53 -0
  44. package/esm/cli/codegen/babel-ast.js +111 -0
  45. package/esm/cli/codegen/barrel.d.ts +7 -2
  46. package/esm/cli/codegen/barrel.js +161 -103
  47. package/esm/cli/codegen/client.js +61 -0
  48. package/esm/cli/codegen/custom-mutations.d.ts +2 -12
  49. package/esm/cli/codegen/custom-mutations.js +83 -124
  50. package/esm/cli/codegen/custom-queries.d.ts +2 -10
  51. package/esm/cli/codegen/custom-queries.js +204 -336
  52. package/esm/cli/codegen/gql-ast.js +23 -2
  53. package/esm/cli/codegen/index.d.ts +3 -0
  54. package/esm/cli/codegen/index.js +69 -2
  55. package/esm/cli/codegen/invalidation.d.ts +20 -0
  56. package/esm/cli/codegen/invalidation.js +291 -0
  57. package/esm/cli/codegen/mutation-keys.d.ts +24 -0
  58. package/esm/cli/codegen/mutation-keys.js +211 -0
  59. package/esm/cli/codegen/mutations.d.ts +5 -19
  60. package/esm/cli/codegen/mutations.js +353 -384
  61. package/esm/cli/codegen/orm/barrel.d.ts +1 -1
  62. package/esm/cli/codegen/orm/barrel.js +10 -11
  63. package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
  64. package/esm/cli/codegen/orm/client-generator.js +76 -78
  65. package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  66. package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
  67. package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
  68. package/esm/cli/codegen/orm/input-types-generator.js +393 -148
  69. package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
  70. package/esm/cli/codegen/orm/model-generator.js +197 -235
  71. package/esm/cli/codegen/queries.d.ts +4 -12
  72. package/esm/cli/codegen/queries.js +628 -391
  73. package/esm/cli/codegen/query-keys.d.ts +15 -0
  74. package/esm/cli/codegen/query-keys.js +441 -0
  75. package/esm/cli/codegen/scalars.js +1 -0
  76. package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
  77. package/esm/cli/codegen/schema-types-generator.js +54 -175
  78. package/esm/cli/codegen/type-resolver.d.ts +1 -30
  79. package/esm/cli/codegen/type-resolver.js +0 -49
  80. package/esm/cli/codegen/types.d.ts +1 -1
  81. package/esm/cli/codegen/types.js +44 -22
  82. package/esm/cli/codegen/utils.d.ts +6 -0
  83. package/esm/cli/codegen/utils.js +18 -0
  84. package/esm/types/config.d.ts +75 -0
  85. package/esm/types/config.js +18 -0
  86. package/package.json +6 -4
  87. package/types/config.d.ts +75 -0
  88. package/types/config.js +19 -1
  89. package/cli/codegen/ts-ast.d.ts +0 -124
  90. package/cli/codegen/ts-ast.js +0 -280
  91. package/esm/cli/codegen/ts-ast.d.ts +0 -124
  92. package/esm/cli/codegen/ts-ast.js +0 -260
@@ -1,5 +1,6 @@
1
- import { createProject, createSourceFile, getMinimalFormattedOutput, createFileHeader, createInterface, createTypeAlias, addSectionComment, } from '../ts-ast';
2
- import { getTableNames, getFilterTypeName, getConditionTypeName, getOrderByTypeName, isRelationField, } from '../utils';
1
+ import * as t from '@babel/types';
2
+ import { generateCode, addLineComment } from '../babel-ast';
3
+ import { getTableNames, getFilterTypeName, getConditionTypeName, getOrderByTypeName, isRelationField, getGeneratedFileHeader, } from '../utils';
3
4
  import { pluralize } from 'inflekt';
4
5
  import { getTypeBaseName } from '../type-resolver';
5
6
  import { scalarToTsType, scalarToFilterType } from '../scalars';
@@ -57,6 +58,86 @@ function typeRefToTs(typeRef) {
57
58
  function isRequired(typeRef) {
58
59
  return typeRef.kind === 'NON_NULL';
59
60
  }
61
+ // ============================================================================
62
+ // Babel AST Helper Functions
63
+ // ============================================================================
64
+ /**
65
+ * Parse a type string into a TSType node
66
+ */
67
+ function parseTypeString(typeStr) {
68
+ // Handle union types like "string | null"
69
+ if (typeStr.includes(' | ')) {
70
+ const parts = typeStr.split(' | ').map((p) => p.trim());
71
+ return t.tsUnionType(parts.map((p) => parseTypeString(p)));
72
+ }
73
+ // Handle array types like "string[]"
74
+ if (typeStr.endsWith('[]')) {
75
+ const elementType = typeStr.slice(0, -2);
76
+ return t.tsArrayType(parseTypeString(elementType));
77
+ }
78
+ // Handle generic types like "Record<string, unknown>"
79
+ if (typeStr.includes('<')) {
80
+ const match = typeStr.match(/^([^<]+)<(.+)>$/);
81
+ if (match) {
82
+ const [, baseName, params] = match;
83
+ const typeParams = params.split(',').map((p) => parseTypeString(p.trim()));
84
+ return t.tsTypeReference(t.identifier(baseName), t.tsTypeParameterInstantiation(typeParams));
85
+ }
86
+ }
87
+ // Handle primitive types
88
+ switch (typeStr) {
89
+ case 'string':
90
+ return t.tsStringKeyword();
91
+ case 'number':
92
+ return t.tsNumberKeyword();
93
+ case 'boolean':
94
+ return t.tsBooleanKeyword();
95
+ case 'null':
96
+ return t.tsNullKeyword();
97
+ case 'unknown':
98
+ return t.tsUnknownKeyword();
99
+ default:
100
+ return t.tsTypeReference(t.identifier(typeStr));
101
+ }
102
+ }
103
+ /**
104
+ * Create an interface property signature
105
+ */
106
+ function createPropertySignature(name, typeStr, optional) {
107
+ const prop = t.tsPropertySignature(t.identifier(name), t.tsTypeAnnotation(parseTypeString(typeStr)));
108
+ prop.optional = optional;
109
+ return prop;
110
+ }
111
+ /**
112
+ * Create an exported interface declaration
113
+ */
114
+ function createExportedInterface(name, properties) {
115
+ const props = properties.map((p) => createPropertySignature(p.name, p.type, p.optional));
116
+ const body = t.tsInterfaceBody(props);
117
+ const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(name), null, null, body);
118
+ return t.exportNamedDeclaration(interfaceDecl);
119
+ }
120
+ /**
121
+ * Create an exported type alias declaration
122
+ */
123
+ function createExportedTypeAlias(name, typeStr) {
124
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(name), null, parseTypeString(typeStr));
125
+ return t.exportNamedDeclaration(typeAlias);
126
+ }
127
+ /**
128
+ * Create a union type from string literals
129
+ */
130
+ function createStringLiteralUnion(values) {
131
+ return t.tsUnionType(values.map((v) => t.tsLiteralType(t.stringLiteral(v))));
132
+ }
133
+ /**
134
+ * Add a section comment to the first statement in an array
135
+ */
136
+ function addSectionComment(statements, sectionName) {
137
+ if (statements.length > 0) {
138
+ addLineComment(statements[0], `============ ${sectionName} ============`);
139
+ }
140
+ }
60
141
  /** Configuration for all scalar filter types - matches PostGraphile's generated filters */
61
142
  const SCALAR_FILTER_CONFIGS = [
62
143
  {
@@ -112,6 +193,22 @@ const SCALAR_FILTER_CONFIGS = [
112
193
  operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'],
113
194
  },
114
195
  { name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
196
+ // List filters (for array fields like string[], int[], uuid[])
197
+ {
198
+ name: 'StringListFilter',
199
+ tsType: 'string[]',
200
+ operators: ['equality', 'distinct', 'comparison', 'listArray'],
201
+ },
202
+ {
203
+ name: 'IntListFilter',
204
+ tsType: 'number[]',
205
+ operators: ['equality', 'distinct', 'comparison', 'listArray'],
206
+ },
207
+ {
208
+ name: 'UUIDListFilter',
209
+ tsType: 'string[]',
210
+ operators: ['equality', 'distinct', 'comparison', 'listArray'],
211
+ },
115
212
  ];
116
213
  /**
117
214
  * Build filter properties based on operator sets
@@ -151,16 +248,24 @@ function buildScalarFilterProperties(config) {
151
248
  if (operators.includes('fulltext')) {
152
249
  props.push({ name: 'matches', type: 'string', optional: true });
153
250
  }
251
+ // List/Array operators (contains, overlaps, anyEqualTo, etc.)
252
+ if (operators.includes('listArray')) {
253
+ // Extract base type from array type (e.g., 'string[]' -> 'string')
254
+ const baseType = tsType.replace('[]', '');
255
+ props.push({ name: 'contains', type: tsType, optional: true }, { name: 'containedBy', type: tsType, optional: true }, { name: 'overlaps', type: tsType, optional: true }, { name: 'anyEqualTo', type: baseType, optional: true }, { name: 'anyNotEqualTo', type: baseType, optional: true }, { name: 'anyLessThan', type: baseType, optional: true }, { name: 'anyLessThanOrEqualTo', type: baseType, optional: true }, { name: 'anyGreaterThan', type: baseType, optional: true }, { name: 'anyGreaterThanOrEqualTo', type: baseType, optional: true });
256
+ }
154
257
  return props;
155
258
  }
156
259
  /**
157
- * Add scalar filter types to source file using ts-morph
260
+ * Generate scalar filter type statements
158
261
  */
159
- function addScalarFilterTypes(sourceFile) {
160
- addSectionComment(sourceFile, 'Scalar Filter Types');
262
+ function generateScalarFilterTypes() {
263
+ const statements = [];
161
264
  for (const config of SCALAR_FILTER_CONFIGS) {
162
- sourceFile.addInterface(createInterface(config.name, buildScalarFilterProperties(config)));
265
+ statements.push(createExportedInterface(config.name, buildScalarFilterProperties(config)));
163
266
  }
267
+ addSectionComment(statements, 'Scalar Filter Types');
268
+ return statements;
164
269
  }
165
270
  // ============================================================================
166
271
  // Enum Types Collector
@@ -189,19 +294,24 @@ function collectEnumTypesFromTables(tables, typeRegistry) {
189
294
  return enumTypes;
190
295
  }
191
296
  /**
192
- * Add enum types to source file
297
+ * Generate enum type statements
193
298
  */
194
- function addEnumTypes(sourceFile, typeRegistry, enumTypeNames) {
299
+ function generateEnumTypes(typeRegistry, enumTypeNames) {
195
300
  if (enumTypeNames.size === 0)
196
- return;
197
- addSectionComment(sourceFile, 'Enum Types');
301
+ return [];
302
+ const statements = [];
198
303
  for (const typeName of Array.from(enumTypeNames).sort()) {
199
304
  const typeInfo = typeRegistry.get(typeName);
200
305
  if (!typeInfo || typeInfo.kind !== 'ENUM' || !typeInfo.enumValues)
201
306
  continue;
202
- const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
203
- sourceFile.addTypeAlias(createTypeAlias(typeName, values));
307
+ const unionType = createStringLiteralUnion(typeInfo.enumValues);
308
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
309
+ statements.push(t.exportNamedDeclaration(typeAlias));
310
+ }
311
+ if (statements.length > 0) {
312
+ addSectionComment(statements, 'Enum Types');
204
313
  }
314
+ return statements;
205
315
  }
206
316
  // ============================================================================
207
317
  // Entity Types Generator (AST-based)
@@ -226,40 +336,47 @@ function buildEntityProperties(table) {
226
336
  return properties;
227
337
  }
228
338
  /**
229
- * Add entity type interface for a table
339
+ * Generate entity type statements
230
340
  */
231
- function addEntityType(sourceFile, table) {
232
- const { typeName } = getTableNames(table);
233
- sourceFile.addInterface(createInterface(typeName, buildEntityProperties(table)));
234
- }
235
- /**
236
- * Add all entity types
237
- */
238
- function addEntityTypes(sourceFile, tables) {
239
- addSectionComment(sourceFile, 'Entity Types');
341
+ function generateEntityTypes(tables) {
342
+ const statements = [];
240
343
  for (const table of tables) {
241
- addEntityType(sourceFile, table);
344
+ const { typeName } = getTableNames(table);
345
+ statements.push(createExportedInterface(typeName, buildEntityProperties(table)));
346
+ }
347
+ if (statements.length > 0) {
348
+ addSectionComment(statements, 'Entity Types');
242
349
  }
350
+ return statements;
243
351
  }
244
352
  // ============================================================================
245
353
  // Relation Helper Types Generator (AST-based)
246
354
  // ============================================================================
247
355
  /**
248
- * Add relation helper types (ConnectionResult, PageInfo)
356
+ * Generate relation helper type statements (ConnectionResult, PageInfo)
249
357
  */
250
- function addRelationHelperTypes(sourceFile) {
251
- addSectionComment(sourceFile, 'Relation Helper Types');
252
- sourceFile.addInterface(createInterface('ConnectionResult<T>', [
253
- { name: 'nodes', type: 'T[]', optional: false },
254
- { name: 'totalCount', type: 'number', optional: false },
255
- { name: 'pageInfo', type: 'PageInfo', optional: false },
256
- ]));
257
- sourceFile.addInterface(createInterface('PageInfo', [
358
+ function generateRelationHelperTypes() {
359
+ const statements = [];
360
+ // ConnectionResult<T> interface with type parameter
361
+ const connectionResultProps = [
362
+ createPropertySignature('nodes', 'T[]', false),
363
+ createPropertySignature('totalCount', 'number', false),
364
+ createPropertySignature('pageInfo', 'PageInfo', false),
365
+ ];
366
+ const connectionResultBody = t.tsInterfaceBody(connectionResultProps);
367
+ const connectionResultDecl = t.tsInterfaceDeclaration(t.identifier('ConnectionResult'), t.tsTypeParameterDeclaration([
368
+ t.tsTypeParameter(null, null, 'T'),
369
+ ]), null, connectionResultBody);
370
+ statements.push(t.exportNamedDeclaration(connectionResultDecl));
371
+ // PageInfo interface
372
+ statements.push(createExportedInterface('PageInfo', [
258
373
  { name: 'hasNextPage', type: 'boolean', optional: false },
259
374
  { name: 'hasPreviousPage', type: 'boolean', optional: false },
260
375
  { name: 'startCursor', type: 'string | null', optional: true },
261
376
  { name: 'endCursor', type: 'string | null', optional: true },
262
377
  ]));
378
+ addSectionComment(statements, 'Relation Helper Types');
379
+ return statements;
263
380
  }
264
381
  // ============================================================================
265
382
  // Entity Relation Types Generator (AST-based)
@@ -332,44 +449,65 @@ function buildEntityRelationProperties(table, tableByName) {
332
449
  return properties;
333
450
  }
334
451
  /**
335
- * Add entity relation types
452
+ * Generate entity relation type statements
336
453
  */
337
- function addEntityRelationTypes(sourceFile, tables, tableByName) {
338
- addSectionComment(sourceFile, 'Entity Relation Types');
454
+ function generateEntityRelationTypes(tables, tableByName) {
455
+ const statements = [];
339
456
  for (const table of tables) {
340
457
  const { typeName } = getTableNames(table);
341
- sourceFile.addInterface(createInterface(`${typeName}Relations`, buildEntityRelationProperties(table, tableByName)));
458
+ statements.push(createExportedInterface(`${typeName}Relations`, buildEntityRelationProperties(table, tableByName)));
342
459
  }
460
+ if (statements.length > 0) {
461
+ addSectionComment(statements, 'Entity Relation Types');
462
+ }
463
+ return statements;
343
464
  }
344
465
  /**
345
- * Add entity types with relations (intersection types)
466
+ * Generate entity types with relations (intersection types)
346
467
  */
347
- function addEntityWithRelations(sourceFile, tables) {
348
- addSectionComment(sourceFile, 'Entity Types With Relations');
468
+ function generateEntityWithRelations(tables) {
469
+ const statements = [];
349
470
  for (const table of tables) {
350
471
  const { typeName } = getTableNames(table);
351
- sourceFile.addTypeAlias(createTypeAlias(`${typeName}WithRelations`, `${typeName} & ${typeName}Relations`));
472
+ statements.push(createExportedTypeAlias(`${typeName}WithRelations`, `${typeName} & ${typeName}Relations`));
473
+ }
474
+ if (statements.length > 0) {
475
+ addSectionComment(statements, 'Entity Types With Relations');
352
476
  }
477
+ return statements;
353
478
  }
354
479
  // ============================================================================
355
480
  // Entity Select Types Generator (AST-based)
356
481
  // ============================================================================
357
482
  /**
358
- * Build the type string for a Select type (as object type literal)
483
+ * Build the Select type as a TSTypeLiteral
359
484
  */
360
- function buildSelectTypeBody(table, tableByName) {
361
- const lines = ['{'];
485
+ function buildSelectTypeLiteral(table, tableByName) {
486
+ const members = [];
362
487
  // Add scalar fields
363
488
  for (const field of table.fields) {
364
489
  if (!isRelationField(field.name, table)) {
365
- lines.push(`${field.name}?: boolean;`);
490
+ const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(t.tsBooleanKeyword()));
491
+ prop.optional = true;
492
+ members.push(prop);
366
493
  }
367
494
  }
368
495
  // Add belongsTo relations
369
496
  for (const relation of table.relations.belongsTo) {
370
497
  if (relation.fieldName) {
371
498
  const relatedTypeName = getRelatedTypeName(relation.referencesTable, tableByName);
372
- lines.push(`${relation.fieldName}?: boolean | { select?: ${relatedTypeName}Select };`);
499
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
500
+ t.tsBooleanKeyword(),
501
+ t.tsTypeLiteral([
502
+ (() => {
503
+ const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
504
+ selectProp.optional = true;
505
+ return selectProp;
506
+ })(),
507
+ ]),
508
+ ])));
509
+ prop.optional = true;
510
+ members.push(prop);
373
511
  }
374
512
  }
375
513
  // Add hasMany relations
@@ -378,12 +516,33 @@ function buildSelectTypeBody(table, tableByName) {
378
516
  const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
379
517
  const filterName = getRelatedFilterName(relation.referencedByTable, tableByName);
380
518
  const orderByName = getRelatedOrderByName(relation.referencedByTable, tableByName);
381
- lines.push(`${relation.fieldName}?: boolean | {`);
382
- lines.push(` select?: ${relatedTypeName}Select;`);
383
- lines.push(` first?: number;`);
384
- lines.push(` filter?: ${filterName};`);
385
- lines.push(` orderBy?: ${orderByName}[];`);
386
- lines.push(`};`);
519
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
520
+ t.tsBooleanKeyword(),
521
+ t.tsTypeLiteral([
522
+ (() => {
523
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
524
+ p.optional = true;
525
+ return p;
526
+ })(),
527
+ (() => {
528
+ const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
529
+ p.optional = true;
530
+ return p;
531
+ })(),
532
+ (() => {
533
+ const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
534
+ p.optional = true;
535
+ return p;
536
+ })(),
537
+ (() => {
538
+ const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
539
+ p.optional = true;
540
+ return p;
541
+ })(),
542
+ ]),
543
+ ])));
544
+ prop.optional = true;
545
+ members.push(prop);
387
546
  }
388
547
  }
389
548
  // Add manyToMany relations
@@ -392,33 +551,69 @@ function buildSelectTypeBody(table, tableByName) {
392
551
  const relatedTypeName = getRelatedTypeName(relation.rightTable, tableByName);
393
552
  const filterName = getRelatedFilterName(relation.rightTable, tableByName);
394
553
  const orderByName = getRelatedOrderByName(relation.rightTable, tableByName);
395
- lines.push(`${relation.fieldName}?: boolean | {`);
396
- lines.push(` select?: ${relatedTypeName}Select;`);
397
- lines.push(` first?: number;`);
398
- lines.push(` filter?: ${filterName};`);
399
- lines.push(` orderBy?: ${orderByName}[];`);
400
- lines.push(`};`);
554
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
555
+ t.tsBooleanKeyword(),
556
+ t.tsTypeLiteral([
557
+ (() => {
558
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
559
+ p.optional = true;
560
+ return p;
561
+ })(),
562
+ (() => {
563
+ const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
564
+ p.optional = true;
565
+ return p;
566
+ })(),
567
+ (() => {
568
+ const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
569
+ p.optional = true;
570
+ return p;
571
+ })(),
572
+ (() => {
573
+ const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
574
+ p.optional = true;
575
+ return p;
576
+ })(),
577
+ ]),
578
+ ])));
579
+ prop.optional = true;
580
+ members.push(prop);
401
581
  }
402
582
  }
403
583
  // Add hasOne relations
404
584
  for (const relation of table.relations.hasOne) {
405
585
  if (relation.fieldName) {
406
586
  const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
407
- lines.push(`${relation.fieldName}?: boolean | { select?: ${relatedTypeName}Select };`);
587
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
588
+ t.tsBooleanKeyword(),
589
+ t.tsTypeLiteral([
590
+ (() => {
591
+ const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
592
+ selectProp.optional = true;
593
+ return selectProp;
594
+ })(),
595
+ ]),
596
+ ])));
597
+ prop.optional = true;
598
+ members.push(prop);
408
599
  }
409
600
  }
410
- lines.push('}');
411
- return lines.join('\n');
601
+ return t.tsTypeLiteral(members);
412
602
  }
413
603
  /**
414
- * Add entity Select types
604
+ * Generate entity Select type statements
415
605
  */
416
- function addEntitySelectTypes(sourceFile, tables, tableByName) {
417
- addSectionComment(sourceFile, 'Entity Select Types');
606
+ function generateEntitySelectTypes(tables, tableByName) {
607
+ const statements = [];
418
608
  for (const table of tables) {
419
609
  const { typeName } = getTableNames(table);
420
- sourceFile.addTypeAlias(createTypeAlias(`${typeName}Select`, buildSelectTypeBody(table, tableByName)));
610
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(`${typeName}Select`), null, buildSelectTypeLiteral(table, tableByName));
611
+ statements.push(t.exportNamedDeclaration(typeAlias));
421
612
  }
613
+ if (statements.length > 0) {
614
+ addSectionComment(statements, 'Entity Select Types');
615
+ }
616
+ return statements;
422
617
  }
423
618
  // ============================================================================
424
619
  // Table Filter Types Generator (AST-based)
@@ -449,14 +644,18 @@ function buildTableFilterProperties(table) {
449
644
  return properties;
450
645
  }
451
646
  /**
452
- * Add table filter types
647
+ * Generate table filter type statements
453
648
  */
454
- function addTableFilterTypes(sourceFile, tables) {
455
- addSectionComment(sourceFile, 'Table Filter Types');
649
+ function generateTableFilterTypes(tables) {
650
+ const statements = [];
456
651
  for (const table of tables) {
457
652
  const filterName = getFilterTypeName(table);
458
- sourceFile.addInterface(createInterface(filterName, buildTableFilterProperties(table)));
653
+ statements.push(createExportedInterface(filterName, buildTableFilterProperties(table)));
654
+ }
655
+ if (statements.length > 0) {
656
+ addSectionComment(statements, 'Table Filter Types');
459
657
  }
658
+ return statements;
460
659
  }
461
660
  // ============================================================================
462
661
  // Condition Types Generator (AST-based)
@@ -482,22 +681,26 @@ function buildTableConditionProperties(table) {
482
681
  return properties;
483
682
  }
484
683
  /**
485
- * Add table condition types
684
+ * Generate table condition type statements
486
685
  */
487
- function addTableConditionTypes(sourceFile, tables) {
488
- addSectionComment(sourceFile, 'Table Condition Types');
686
+ function generateTableConditionTypes(tables) {
687
+ const statements = [];
489
688
  for (const table of tables) {
490
689
  const conditionName = getConditionTypeName(table);
491
- sourceFile.addInterface(createInterface(conditionName, buildTableConditionProperties(table)));
690
+ statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table)));
691
+ }
692
+ if (statements.length > 0) {
693
+ addSectionComment(statements, 'Table Condition Types');
492
694
  }
695
+ return statements;
493
696
  }
494
697
  // ============================================================================
495
698
  // OrderBy Types Generator (AST-based)
496
699
  // ============================================================================
497
700
  /**
498
- * Build OrderBy union type string
701
+ * Build OrderBy union type values
499
702
  */
500
- function buildOrderByUnion(table) {
703
+ function buildOrderByValues(table) {
501
704
  const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
502
705
  for (const field of table.fields) {
503
706
  if (isRelationField(field.name, table))
@@ -506,19 +709,24 @@ function buildOrderByUnion(table) {
506
709
  values.push(`${upperSnake}_ASC`);
507
710
  values.push(`${upperSnake}_DESC`);
508
711
  }
509
- return values.map((v) => `'${v}'`).join(' | ');
712
+ return values;
510
713
  }
511
714
  /**
512
- * Add OrderBy types
513
- * Uses inflection from table metadata for correct pluralization
715
+ * Generate OrderBy type statements
514
716
  */
515
- function addOrderByTypes(sourceFile, tables) {
516
- addSectionComment(sourceFile, 'OrderBy Types');
717
+ function generateOrderByTypes(tables) {
718
+ const statements = [];
517
719
  for (const table of tables) {
518
- // Use getOrderByTypeName which respects table.inflection.orderByType
519
720
  const enumName = getOrderByTypeName(table);
520
- sourceFile.addTypeAlias(createTypeAlias(enumName, buildOrderByUnion(table)));
721
+ const values = buildOrderByValues(table);
722
+ const unionType = createStringLiteralUnion(values);
723
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(enumName), null, unionType);
724
+ statements.push(t.exportNamedDeclaration(typeAlias));
521
725
  }
726
+ if (statements.length > 0) {
727
+ addSectionComment(statements, 'OrderBy Types');
728
+ }
729
+ return statements;
522
730
  }
523
731
  // ============================================================================
524
732
  // CRUD Input Types Generator (AST-based)
@@ -541,27 +749,30 @@ function buildCreateDataFields(table) {
541
749
  return fields;
542
750
  }
543
751
  /**
544
- * Generate Create input interface as formatted string.
545
- *
546
- * ts-morph doesn't handle nested object types in interface properties well,
547
- * so we build this manually with pre-doubled indentation (4→2, 8→4) since
548
- * getMinimalFormattedOutput halves all indentation.
752
+ * Build Create input interface as AST
549
753
  */
550
754
  function buildCreateInputInterface(table) {
551
755
  const { typeName, singularName } = getTableNames(table);
552
756
  const fields = buildCreateDataFields(table);
553
- const lines = [
554
- `export interface Create${typeName}Input {`,
555
- ` clientMutationId?: string;`,
556
- ` ${singularName}: {`,
757
+ // Build the nested object type for the entity data
758
+ const nestedProps = fields.map((field) => {
759
+ const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(parseTypeString(field.type)));
760
+ prop.optional = field.optional;
761
+ return prop;
762
+ });
763
+ const nestedObjectType = t.tsTypeLiteral(nestedProps);
764
+ // Build the main interface properties
765
+ const mainProps = [
766
+ (() => {
767
+ const prop = t.tsPropertySignature(t.identifier('clientMutationId'), t.tsTypeAnnotation(t.tsStringKeyword()));
768
+ prop.optional = true;
769
+ return prop;
770
+ })(),
771
+ t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(nestedObjectType)),
557
772
  ];
558
- for (const field of fields) {
559
- const opt = field.optional ? '?' : '';
560
- lines.push(` ${field.name}${opt}: ${field.type};`);
561
- }
562
- lines.push(' };');
563
- lines.push('}');
564
- return lines.join('\n');
773
+ const body = t.tsInterfaceBody(mainProps);
774
+ const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(`Create${typeName}Input`), null, null, body);
775
+ return t.exportNamedDeclaration(interfaceDecl);
565
776
  }
566
777
  /**
567
778
  * Build Patch type properties
@@ -584,35 +795,41 @@ function buildPatchProperties(table) {
584
795
  return properties;
585
796
  }
586
797
  /**
587
- * Add CRUD input types for a table
798
+ * Generate CRUD input type statements for a table
588
799
  */
589
- function addCrudInputTypes(sourceFile, table) {
800
+ function generateCrudInputTypes(table) {
801
+ const statements = [];
590
802
  const { typeName } = getTableNames(table);
591
803
  const patchName = `${typeName}Patch`;
592
- // Create input - build as raw statement due to nested object type formatting
593
- sourceFile.addStatements(buildCreateInputInterface(table));
804
+ // Create input
805
+ statements.push(buildCreateInputInterface(table));
594
806
  // Patch interface
595
- sourceFile.addInterface(createInterface(patchName, buildPatchProperties(table)));
807
+ statements.push(createExportedInterface(patchName, buildPatchProperties(table)));
596
808
  // Update input
597
- sourceFile.addInterface(createInterface(`Update${typeName}Input`, [
809
+ statements.push(createExportedInterface(`Update${typeName}Input`, [
598
810
  { name: 'clientMutationId', type: 'string', optional: true },
599
811
  { name: 'id', type: 'string', optional: false },
600
812
  { name: 'patch', type: patchName, optional: false },
601
813
  ]));
602
814
  // Delete input
603
- sourceFile.addInterface(createInterface(`Delete${typeName}Input`, [
815
+ statements.push(createExportedInterface(`Delete${typeName}Input`, [
604
816
  { name: 'clientMutationId', type: 'string', optional: true },
605
817
  { name: 'id', type: 'string', optional: false },
606
818
  ]));
819
+ return statements;
607
820
  }
608
821
  /**
609
- * Add all CRUD input types
822
+ * Generate all CRUD input type statements
610
823
  */
611
- function addAllCrudInputTypes(sourceFile, tables) {
612
- addSectionComment(sourceFile, 'CRUD Input Types');
824
+ function generateAllCrudInputTypes(tables) {
825
+ const statements = [];
613
826
  for (const table of tables) {
614
- addCrudInputTypes(sourceFile, table);
827
+ statements.push(...generateCrudInputTypes(table));
615
828
  }
829
+ if (statements.length > 0) {
830
+ addSectionComment(statements, 'CRUD Input Types');
831
+ }
832
+ return statements;
616
833
  }
617
834
  // ============================================================================
618
835
  // Custom Input Types Generator (AST-based)
@@ -640,7 +857,7 @@ export function collectInputTypeNames(operations) {
640
857
  }
641
858
  /**
642
859
  * Build a set of exact table CRUD input type names to skip
643
- * These are generated by addAllCrudInputTypes, so we don't need to regenerate them
860
+ * These are generated by generateAllCrudInputTypes, so we don't need to regenerate them
644
861
  */
645
862
  function buildTableCrudTypeNames(tables) {
646
863
  const crudTypes = new Set();
@@ -655,10 +872,10 @@ function buildTableCrudTypeNames(tables) {
655
872
  return crudTypes;
656
873
  }
657
874
  /**
658
- * Add custom input types from TypeRegistry
875
+ * Generate custom input type statements from TypeRegistry
659
876
  */
660
- function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrudTypes) {
661
- addSectionComment(sourceFile, 'Custom Input Types (from schema)');
877
+ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes) {
878
+ const statements = [];
662
879
  const generatedTypes = new Set();
663
880
  const typesToGenerate = new Set(Array.from(usedInputTypes));
664
881
  // Filter out types we've already generated (exact matches for table CRUD types only)
@@ -681,8 +898,11 @@ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrud
681
898
  generatedTypes.add(typeName);
682
899
  const typeInfo = typeRegistry.get(typeName);
683
900
  if (!typeInfo) {
684
- sourceFile.addStatements(`// Type '${typeName}' not found in schema`);
685
- sourceFile.addTypeAlias(createTypeAlias(typeName, 'Record<string, unknown>'));
901
+ // Add comment for missing type
902
+ const commentStmt = t.emptyStatement();
903
+ addLineComment(commentStmt, ` Type '${typeName}' not found in schema`);
904
+ statements.push(commentStmt);
905
+ statements.push(createExportedTypeAlias(typeName, 'Record<string, unknown>'));
686
906
  continue;
687
907
  }
688
908
  if (typeInfo.kind === 'INPUT_OBJECT' && typeInfo.inputFields) {
@@ -699,17 +919,25 @@ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrud
699
919
  typesToGenerate.add(baseType);
700
920
  }
701
921
  }
702
- sourceFile.addInterface(createInterface(typeName, properties));
922
+ statements.push(createExportedInterface(typeName, properties));
703
923
  }
704
924
  else if (typeInfo.kind === 'ENUM' && typeInfo.enumValues) {
705
- const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
706
- sourceFile.addTypeAlias(createTypeAlias(typeName, values));
925
+ const unionType = createStringLiteralUnion(typeInfo.enumValues);
926
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
927
+ statements.push(t.exportNamedDeclaration(typeAlias));
707
928
  }
708
929
  else {
709
- sourceFile.addStatements(`// Type '${typeName}' is ${typeInfo.kind}`);
710
- sourceFile.addTypeAlias(createTypeAlias(typeName, 'unknown'));
930
+ // Add comment for unsupported type kind
931
+ const commentStmt = t.emptyStatement();
932
+ addLineComment(commentStmt, ` Type '${typeName}' is ${typeInfo.kind}`);
933
+ statements.push(commentStmt);
934
+ statements.push(createExportedTypeAlias(typeName, 'unknown'));
711
935
  }
712
936
  }
937
+ if (statements.length > 0) {
938
+ addSectionComment(statements, 'Custom Input Types (from schema)');
939
+ }
940
+ return statements;
713
941
  }
714
942
  // ============================================================================
715
943
  // Payload/Return Types Generator (AST-based)
@@ -729,10 +957,10 @@ export function collectPayloadTypeNames(operations) {
729
957
  return payloadTypes;
730
958
  }
731
959
  /**
732
- * Add payload/return types
960
+ * Generate payload/return type statements
733
961
  */
734
- function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGeneratedTypes) {
735
- addSectionComment(sourceFile, 'Payload/Return Types (for custom operations)');
962
+ function generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes) {
963
+ const statements = [];
736
964
  const generatedTypes = new Set(alreadyGeneratedTypes);
737
965
  const typesToGenerate = new Set(Array.from(usedPayloadTypes));
738
966
  const skipTypes = new Set([
@@ -790,63 +1018,77 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
790
1018
  }
791
1019
  }
792
1020
  }
793
- sourceFile.addInterface(createInterface(typeName, interfaceProps));
794
- // Build Select type (no indentation - ts-morph adds it)
795
- const selectLines = ['{'];
1021
+ statements.push(createExportedInterface(typeName, interfaceProps));
1022
+ // Build Select type
1023
+ const selectMembers = [];
796
1024
  for (const field of typeInfo.fields) {
797
1025
  const baseType = getTypeBaseName(field.type);
798
1026
  if (baseType === 'Query' || baseType === 'Mutation')
799
1027
  continue;
800
1028
  const nestedType = baseType ? typeRegistry.get(baseType) : null;
1029
+ let propType;
801
1030
  if (nestedType?.kind === 'OBJECT') {
802
- selectLines.push(`${field.name}?: boolean | { select?: ${baseType}Select };`);
1031
+ propType = t.tsUnionType([
1032
+ t.tsBooleanKeyword(),
1033
+ t.tsTypeLiteral([
1034
+ (() => {
1035
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${baseType}Select`))));
1036
+ p.optional = true;
1037
+ return p;
1038
+ })(),
1039
+ ]),
1040
+ ]);
803
1041
  }
804
1042
  else {
805
- selectLines.push(`${field.name}?: boolean;`);
1043
+ propType = t.tsBooleanKeyword();
806
1044
  }
1045
+ const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(propType));
1046
+ prop.optional = true;
1047
+ selectMembers.push(prop);
807
1048
  }
808
- selectLines.push('}');
809
- sourceFile.addTypeAlias(createTypeAlias(`${typeName}Select`, selectLines.join('\n')));
1049
+ const selectTypeAlias = t.tsTypeAliasDeclaration(t.identifier(`${typeName}Select`), null, t.tsTypeLiteral(selectMembers));
1050
+ statements.push(t.exportNamedDeclaration(selectTypeAlias));
1051
+ }
1052
+ if (statements.length > 0) {
1053
+ addSectionComment(statements, 'Payload/Return Types (for custom operations)');
810
1054
  }
1055
+ return statements;
811
1056
  }
812
1057
  // ============================================================================
813
1058
  // Main Generator (AST-based)
814
1059
  // ============================================================================
815
1060
  /**
816
- * Generate comprehensive input-types.ts file using ts-morph AST
1061
+ * Generate comprehensive input-types.ts file using Babel AST
817
1062
  */
818
1063
  export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes) {
819
- const project = createProject();
820
- const sourceFile = createSourceFile(project, 'input-types.ts');
821
- // Add file header
822
- sourceFile.insertText(0, createFileHeader('GraphQL types for ORM client') + '\n');
1064
+ const statements = [];
823
1065
  // 1. Scalar filter types
824
- addScalarFilterTypes(sourceFile);
1066
+ statements.push(...generateScalarFilterTypes());
825
1067
  // 2. Enum types used by table fields
826
1068
  if (tables && tables.length > 0) {
827
1069
  const enumTypes = collectEnumTypesFromTables(tables, typeRegistry);
828
- addEnumTypes(sourceFile, typeRegistry, enumTypes);
1070
+ statements.push(...generateEnumTypes(typeRegistry, enumTypes));
829
1071
  }
830
1072
  // 3. Entity and relation types (if tables provided)
831
1073
  if (tables && tables.length > 0) {
832
1074
  const tableByName = new Map(tables.map((table) => [table.name, table]));
833
- addEntityTypes(sourceFile, tables);
834
- addRelationHelperTypes(sourceFile);
835
- addEntityRelationTypes(sourceFile, tables, tableByName);
836
- addEntityWithRelations(sourceFile, tables);
837
- addEntitySelectTypes(sourceFile, tables, tableByName);
1075
+ statements.push(...generateEntityTypes(tables));
1076
+ statements.push(...generateRelationHelperTypes());
1077
+ statements.push(...generateEntityRelationTypes(tables, tableByName));
1078
+ statements.push(...generateEntityWithRelations(tables));
1079
+ statements.push(...generateEntitySelectTypes(tables, tableByName));
838
1080
  // 4. Table filter types
839
- addTableFilterTypes(sourceFile, tables);
1081
+ statements.push(...generateTableFilterTypes(tables));
840
1082
  // 4b. Table condition types (simple equality filter)
841
- addTableConditionTypes(sourceFile, tables);
1083
+ statements.push(...generateTableConditionTypes(tables));
842
1084
  // 5. OrderBy types
843
- addOrderByTypes(sourceFile, tables);
1085
+ statements.push(...generateOrderByTypes(tables));
844
1086
  // 6. CRUD input types
845
- addAllCrudInputTypes(sourceFile, tables);
1087
+ statements.push(...generateAllCrudInputTypes(tables));
846
1088
  }
847
1089
  // 7. Custom input types from TypeRegistry
848
1090
  const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
849
- addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrudTypes);
1091
+ statements.push(...generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes));
850
1092
  // 8. Payload/return types for custom operations
851
1093
  if (usedPayloadTypes && usedPayloadTypes.size > 0) {
852
1094
  const alreadyGeneratedTypes = new Set();
@@ -856,10 +1098,13 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
856
1098
  alreadyGeneratedTypes.add(typeName);
857
1099
  }
858
1100
  }
859
- addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGeneratedTypes);
1101
+ statements.push(...generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes));
860
1102
  }
1103
+ // Generate code with file header
1104
+ const header = getGeneratedFileHeader('GraphQL types for ORM client');
1105
+ const code = generateCode(statements);
861
1106
  return {
862
1107
  fileName: 'input-types.ts',
863
- content: getMinimalFormattedOutput(sourceFile),
1108
+ content: header + '\n' + code,
864
1109
  };
865
1110
  }