@constructive-io/graphql-codegen 2.23.2 → 2.24.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 (90) hide show
  1. package/README.md +147 -2
  2. package/cli/codegen/babel-ast.d.ts +46 -0
  3. package/cli/codegen/babel-ast.js +145 -0
  4. package/cli/codegen/barrel.d.ts +7 -2
  5. package/cli/codegen/barrel.js +159 -97
  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 +246 -335
  11. package/cli/codegen/index.d.ts +3 -0
  12. package/cli/codegen/index.js +72 -3
  13. package/cli/codegen/invalidation.d.ts +20 -0
  14. package/cli/codegen/invalidation.js +327 -0
  15. package/cli/codegen/mutation-keys.d.ts +24 -0
  16. package/cli/codegen/mutation-keys.js +247 -0
  17. package/cli/codegen/mutations.d.ts +3 -19
  18. package/cli/codegen/mutations.js +372 -383
  19. package/cli/codegen/orm/barrel.d.ts +1 -1
  20. package/cli/codegen/orm/barrel.js +42 -10
  21. package/cli/codegen/orm/client-generator.d.ts +1 -19
  22. package/cli/codegen/orm/client-generator.js +108 -77
  23. package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  24. package/cli/codegen/orm/custom-ops-generator.js +192 -235
  25. package/cli/codegen/orm/input-types-generator.d.ts +13 -1
  26. package/cli/codegen/orm/input-types-generator.js +403 -147
  27. package/cli/codegen/orm/model-generator.d.ts +1 -19
  28. package/cli/codegen/orm/model-generator.js +229 -234
  29. package/cli/codegen/queries.d.ts +3 -11
  30. package/cli/codegen/queries.js +582 -389
  31. package/cli/codegen/query-keys.d.ts +15 -0
  32. package/cli/codegen/query-keys.js +477 -0
  33. package/cli/codegen/scalars.js +1 -0
  34. package/cli/codegen/schema-types-generator.d.ts +15 -10
  35. package/cli/codegen/schema-types-generator.js +87 -175
  36. package/cli/codegen/type-resolver.d.ts +1 -30
  37. package/cli/codegen/type-resolver.js +0 -53
  38. package/cli/codegen/types.d.ts +1 -1
  39. package/cli/codegen/types.js +76 -21
  40. package/cli/commands/generate.js +1 -0
  41. package/cli/index.js +1 -0
  42. package/esm/cli/codegen/babel-ast.d.ts +46 -0
  43. package/esm/cli/codegen/babel-ast.js +97 -0
  44. package/esm/cli/codegen/barrel.d.ts +7 -2
  45. package/esm/cli/codegen/barrel.js +126 -97
  46. package/esm/cli/codegen/client.js +61 -0
  47. package/esm/cli/codegen/custom-mutations.d.ts +2 -12
  48. package/esm/cli/codegen/custom-mutations.js +83 -124
  49. package/esm/cli/codegen/custom-queries.d.ts +2 -10
  50. package/esm/cli/codegen/custom-queries.js +214 -336
  51. package/esm/cli/codegen/index.d.ts +3 -0
  52. package/esm/cli/codegen/index.js +68 -2
  53. package/esm/cli/codegen/invalidation.d.ts +20 -0
  54. package/esm/cli/codegen/invalidation.js +291 -0
  55. package/esm/cli/codegen/mutation-keys.d.ts +24 -0
  56. package/esm/cli/codegen/mutation-keys.js +211 -0
  57. package/esm/cli/codegen/mutations.d.ts +3 -19
  58. package/esm/cli/codegen/mutations.js +340 -384
  59. package/esm/cli/codegen/orm/barrel.d.ts +1 -1
  60. package/esm/cli/codegen/orm/barrel.js +10 -11
  61. package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
  62. package/esm/cli/codegen/orm/client-generator.js +76 -78
  63. package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  64. package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
  65. package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
  66. package/esm/cli/codegen/orm/input-types-generator.js +371 -148
  67. package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
  68. package/esm/cli/codegen/orm/model-generator.js +197 -235
  69. package/esm/cli/codegen/queries.d.ts +3 -11
  70. package/esm/cli/codegen/queries.js +550 -390
  71. package/esm/cli/codegen/query-keys.d.ts +15 -0
  72. package/esm/cli/codegen/query-keys.js +441 -0
  73. package/esm/cli/codegen/scalars.js +1 -0
  74. package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
  75. package/esm/cli/codegen/schema-types-generator.js +54 -175
  76. package/esm/cli/codegen/type-resolver.d.ts +1 -30
  77. package/esm/cli/codegen/type-resolver.js +0 -49
  78. package/esm/cli/codegen/types.d.ts +1 -1
  79. package/esm/cli/codegen/types.js +44 -22
  80. package/esm/cli/commands/generate.js +1 -0
  81. package/esm/cli/index.js +1 -0
  82. package/esm/types/config.d.ts +75 -0
  83. package/esm/types/config.js +19 -1
  84. package/package.json +6 -4
  85. package/types/config.d.ts +75 -0
  86. package/types/config.js +20 -2
  87. package/cli/codegen/ts-ast.d.ts +0 -124
  88. package/cli/codegen/ts-ast.js +0 -280
  89. package/esm/cli/codegen/ts-ast.d.ts +0 -124
  90. 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
  {
@@ -154,13 +235,15 @@ function buildScalarFilterProperties(config) {
154
235
  return props;
155
236
  }
156
237
  /**
157
- * Add scalar filter types to source file using ts-morph
238
+ * Generate scalar filter type statements
158
239
  */
159
- function addScalarFilterTypes(sourceFile) {
160
- addSectionComment(sourceFile, 'Scalar Filter Types');
240
+ function generateScalarFilterTypes() {
241
+ const statements = [];
161
242
  for (const config of SCALAR_FILTER_CONFIGS) {
162
- sourceFile.addInterface(createInterface(config.name, buildScalarFilterProperties(config)));
243
+ statements.push(createExportedInterface(config.name, buildScalarFilterProperties(config)));
163
244
  }
245
+ addSectionComment(statements, 'Scalar Filter Types');
246
+ return statements;
164
247
  }
165
248
  // ============================================================================
166
249
  // Enum Types Collector
@@ -189,19 +272,24 @@ function collectEnumTypesFromTables(tables, typeRegistry) {
189
272
  return enumTypes;
190
273
  }
191
274
  /**
192
- * Add enum types to source file
275
+ * Generate enum type statements
193
276
  */
194
- function addEnumTypes(sourceFile, typeRegistry, enumTypeNames) {
277
+ function generateEnumTypes(typeRegistry, enumTypeNames) {
195
278
  if (enumTypeNames.size === 0)
196
- return;
197
- addSectionComment(sourceFile, 'Enum Types');
279
+ return [];
280
+ const statements = [];
198
281
  for (const typeName of Array.from(enumTypeNames).sort()) {
199
282
  const typeInfo = typeRegistry.get(typeName);
200
283
  if (!typeInfo || typeInfo.kind !== 'ENUM' || !typeInfo.enumValues)
201
284
  continue;
202
- const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
203
- sourceFile.addTypeAlias(createTypeAlias(typeName, values));
285
+ const unionType = createStringLiteralUnion(typeInfo.enumValues);
286
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
287
+ statements.push(t.exportNamedDeclaration(typeAlias));
204
288
  }
289
+ if (statements.length > 0) {
290
+ addSectionComment(statements, 'Enum Types');
291
+ }
292
+ return statements;
205
293
  }
206
294
  // ============================================================================
207
295
  // Entity Types Generator (AST-based)
@@ -226,40 +314,47 @@ function buildEntityProperties(table) {
226
314
  return properties;
227
315
  }
228
316
  /**
229
- * Add entity type interface for a table
230
- */
231
- function addEntityType(sourceFile, table) {
232
- const { typeName } = getTableNames(table);
233
- sourceFile.addInterface(createInterface(typeName, buildEntityProperties(table)));
234
- }
235
- /**
236
- * Add all entity types
317
+ * Generate entity type statements
237
318
  */
238
- function addEntityTypes(sourceFile, tables) {
239
- addSectionComment(sourceFile, 'Entity Types');
319
+ function generateEntityTypes(tables) {
320
+ const statements = [];
240
321
  for (const table of tables) {
241
- addEntityType(sourceFile, table);
322
+ const { typeName } = getTableNames(table);
323
+ statements.push(createExportedInterface(typeName, buildEntityProperties(table)));
242
324
  }
325
+ if (statements.length > 0) {
326
+ addSectionComment(statements, 'Entity Types');
327
+ }
328
+ return statements;
243
329
  }
244
330
  // ============================================================================
245
331
  // Relation Helper Types Generator (AST-based)
246
332
  // ============================================================================
247
333
  /**
248
- * Add relation helper types (ConnectionResult, PageInfo)
334
+ * Generate relation helper type statements (ConnectionResult, PageInfo)
249
335
  */
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', [
336
+ function generateRelationHelperTypes() {
337
+ const statements = [];
338
+ // ConnectionResult<T> interface with type parameter
339
+ const connectionResultProps = [
340
+ createPropertySignature('nodes', 'T[]', false),
341
+ createPropertySignature('totalCount', 'number', false),
342
+ createPropertySignature('pageInfo', 'PageInfo', false),
343
+ ];
344
+ const connectionResultBody = t.tsInterfaceBody(connectionResultProps);
345
+ const connectionResultDecl = t.tsInterfaceDeclaration(t.identifier('ConnectionResult'), t.tsTypeParameterDeclaration([
346
+ t.tsTypeParameter(null, null, 'T'),
347
+ ]), null, connectionResultBody);
348
+ statements.push(t.exportNamedDeclaration(connectionResultDecl));
349
+ // PageInfo interface
350
+ statements.push(createExportedInterface('PageInfo', [
258
351
  { name: 'hasNextPage', type: 'boolean', optional: false },
259
352
  { name: 'hasPreviousPage', type: 'boolean', optional: false },
260
353
  { name: 'startCursor', type: 'string | null', optional: true },
261
354
  { name: 'endCursor', type: 'string | null', optional: true },
262
355
  ]));
356
+ addSectionComment(statements, 'Relation Helper Types');
357
+ return statements;
263
358
  }
264
359
  // ============================================================================
265
360
  // Entity Relation Types Generator (AST-based)
@@ -332,44 +427,65 @@ function buildEntityRelationProperties(table, tableByName) {
332
427
  return properties;
333
428
  }
334
429
  /**
335
- * Add entity relation types
430
+ * Generate entity relation type statements
336
431
  */
337
- function addEntityRelationTypes(sourceFile, tables, tableByName) {
338
- addSectionComment(sourceFile, 'Entity Relation Types');
432
+ function generateEntityRelationTypes(tables, tableByName) {
433
+ const statements = [];
339
434
  for (const table of tables) {
340
435
  const { typeName } = getTableNames(table);
341
- sourceFile.addInterface(createInterface(`${typeName}Relations`, buildEntityRelationProperties(table, tableByName)));
436
+ statements.push(createExportedInterface(`${typeName}Relations`, buildEntityRelationProperties(table, tableByName)));
342
437
  }
438
+ if (statements.length > 0) {
439
+ addSectionComment(statements, 'Entity Relation Types');
440
+ }
441
+ return statements;
343
442
  }
344
443
  /**
345
- * Add entity types with relations (intersection types)
444
+ * Generate entity types with relations (intersection types)
346
445
  */
347
- function addEntityWithRelations(sourceFile, tables) {
348
- addSectionComment(sourceFile, 'Entity Types With Relations');
446
+ function generateEntityWithRelations(tables) {
447
+ const statements = [];
349
448
  for (const table of tables) {
350
449
  const { typeName } = getTableNames(table);
351
- sourceFile.addTypeAlias(createTypeAlias(`${typeName}WithRelations`, `${typeName} & ${typeName}Relations`));
450
+ statements.push(createExportedTypeAlias(`${typeName}WithRelations`, `${typeName} & ${typeName}Relations`));
451
+ }
452
+ if (statements.length > 0) {
453
+ addSectionComment(statements, 'Entity Types With Relations');
352
454
  }
455
+ return statements;
353
456
  }
354
457
  // ============================================================================
355
458
  // Entity Select Types Generator (AST-based)
356
459
  // ============================================================================
357
460
  /**
358
- * Build the type string for a Select type (as object type literal)
461
+ * Build the Select type as a TSTypeLiteral
359
462
  */
360
- function buildSelectTypeBody(table, tableByName) {
361
- const lines = ['{'];
463
+ function buildSelectTypeLiteral(table, tableByName) {
464
+ const members = [];
362
465
  // Add scalar fields
363
466
  for (const field of table.fields) {
364
467
  if (!isRelationField(field.name, table)) {
365
- lines.push(`${field.name}?: boolean;`);
468
+ const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(t.tsBooleanKeyword()));
469
+ prop.optional = true;
470
+ members.push(prop);
366
471
  }
367
472
  }
368
473
  // Add belongsTo relations
369
474
  for (const relation of table.relations.belongsTo) {
370
475
  if (relation.fieldName) {
371
476
  const relatedTypeName = getRelatedTypeName(relation.referencesTable, tableByName);
372
- lines.push(`${relation.fieldName}?: boolean | { select?: ${relatedTypeName}Select };`);
477
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
478
+ t.tsBooleanKeyword(),
479
+ t.tsTypeLiteral([
480
+ (() => {
481
+ const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
482
+ selectProp.optional = true;
483
+ return selectProp;
484
+ })(),
485
+ ]),
486
+ ])));
487
+ prop.optional = true;
488
+ members.push(prop);
373
489
  }
374
490
  }
375
491
  // Add hasMany relations
@@ -378,12 +494,33 @@ function buildSelectTypeBody(table, tableByName) {
378
494
  const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
379
495
  const filterName = getRelatedFilterName(relation.referencedByTable, tableByName);
380
496
  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(`};`);
497
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
498
+ t.tsBooleanKeyword(),
499
+ t.tsTypeLiteral([
500
+ (() => {
501
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
502
+ p.optional = true;
503
+ return p;
504
+ })(),
505
+ (() => {
506
+ const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
507
+ p.optional = true;
508
+ return p;
509
+ })(),
510
+ (() => {
511
+ const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
512
+ p.optional = true;
513
+ return p;
514
+ })(),
515
+ (() => {
516
+ const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
517
+ p.optional = true;
518
+ return p;
519
+ })(),
520
+ ]),
521
+ ])));
522
+ prop.optional = true;
523
+ members.push(prop);
387
524
  }
388
525
  }
389
526
  // Add manyToMany relations
@@ -392,33 +529,69 @@ function buildSelectTypeBody(table, tableByName) {
392
529
  const relatedTypeName = getRelatedTypeName(relation.rightTable, tableByName);
393
530
  const filterName = getRelatedFilterName(relation.rightTable, tableByName);
394
531
  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(`};`);
532
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
533
+ t.tsBooleanKeyword(),
534
+ t.tsTypeLiteral([
535
+ (() => {
536
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
537
+ p.optional = true;
538
+ return p;
539
+ })(),
540
+ (() => {
541
+ const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
542
+ p.optional = true;
543
+ return p;
544
+ })(),
545
+ (() => {
546
+ const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
547
+ p.optional = true;
548
+ return p;
549
+ })(),
550
+ (() => {
551
+ const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
552
+ p.optional = true;
553
+ return p;
554
+ })(),
555
+ ]),
556
+ ])));
557
+ prop.optional = true;
558
+ members.push(prop);
401
559
  }
402
560
  }
403
561
  // Add hasOne relations
404
562
  for (const relation of table.relations.hasOne) {
405
563
  if (relation.fieldName) {
406
564
  const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
407
- lines.push(`${relation.fieldName}?: boolean | { select?: ${relatedTypeName}Select };`);
565
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
566
+ t.tsBooleanKeyword(),
567
+ t.tsTypeLiteral([
568
+ (() => {
569
+ const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
570
+ selectProp.optional = true;
571
+ return selectProp;
572
+ })(),
573
+ ]),
574
+ ])));
575
+ prop.optional = true;
576
+ members.push(prop);
408
577
  }
409
578
  }
410
- lines.push('}');
411
- return lines.join('\n');
579
+ return t.tsTypeLiteral(members);
412
580
  }
413
581
  /**
414
- * Add entity Select types
582
+ * Generate entity Select type statements
415
583
  */
416
- function addEntitySelectTypes(sourceFile, tables, tableByName) {
417
- addSectionComment(sourceFile, 'Entity Select Types');
584
+ function generateEntitySelectTypes(tables, tableByName) {
585
+ const statements = [];
418
586
  for (const table of tables) {
419
587
  const { typeName } = getTableNames(table);
420
- sourceFile.addTypeAlias(createTypeAlias(`${typeName}Select`, buildSelectTypeBody(table, tableByName)));
588
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(`${typeName}Select`), null, buildSelectTypeLiteral(table, tableByName));
589
+ statements.push(t.exportNamedDeclaration(typeAlias));
590
+ }
591
+ if (statements.length > 0) {
592
+ addSectionComment(statements, 'Entity Select Types');
421
593
  }
594
+ return statements;
422
595
  }
423
596
  // ============================================================================
424
597
  // Table Filter Types Generator (AST-based)
@@ -449,14 +622,18 @@ function buildTableFilterProperties(table) {
449
622
  return properties;
450
623
  }
451
624
  /**
452
- * Add table filter types
625
+ * Generate table filter type statements
453
626
  */
454
- function addTableFilterTypes(sourceFile, tables) {
455
- addSectionComment(sourceFile, 'Table Filter Types');
627
+ function generateTableFilterTypes(tables) {
628
+ const statements = [];
456
629
  for (const table of tables) {
457
630
  const filterName = getFilterTypeName(table);
458
- sourceFile.addInterface(createInterface(filterName, buildTableFilterProperties(table)));
631
+ statements.push(createExportedInterface(filterName, buildTableFilterProperties(table)));
459
632
  }
633
+ if (statements.length > 0) {
634
+ addSectionComment(statements, 'Table Filter Types');
635
+ }
636
+ return statements;
460
637
  }
461
638
  // ============================================================================
462
639
  // Condition Types Generator (AST-based)
@@ -482,22 +659,26 @@ function buildTableConditionProperties(table) {
482
659
  return properties;
483
660
  }
484
661
  /**
485
- * Add table condition types
662
+ * Generate table condition type statements
486
663
  */
487
- function addTableConditionTypes(sourceFile, tables) {
488
- addSectionComment(sourceFile, 'Table Condition Types');
664
+ function generateTableConditionTypes(tables) {
665
+ const statements = [];
489
666
  for (const table of tables) {
490
667
  const conditionName = getConditionTypeName(table);
491
- sourceFile.addInterface(createInterface(conditionName, buildTableConditionProperties(table)));
668
+ statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table)));
669
+ }
670
+ if (statements.length > 0) {
671
+ addSectionComment(statements, 'Table Condition Types');
492
672
  }
673
+ return statements;
493
674
  }
494
675
  // ============================================================================
495
676
  // OrderBy Types Generator (AST-based)
496
677
  // ============================================================================
497
678
  /**
498
- * Build OrderBy union type string
679
+ * Build OrderBy union type values
499
680
  */
500
- function buildOrderByUnion(table) {
681
+ function buildOrderByValues(table) {
501
682
  const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
502
683
  for (const field of table.fields) {
503
684
  if (isRelationField(field.name, table))
@@ -506,19 +687,24 @@ function buildOrderByUnion(table) {
506
687
  values.push(`${upperSnake}_ASC`);
507
688
  values.push(`${upperSnake}_DESC`);
508
689
  }
509
- return values.map((v) => `'${v}'`).join(' | ');
690
+ return values;
510
691
  }
511
692
  /**
512
- * Add OrderBy types
513
- * Uses inflection from table metadata for correct pluralization
693
+ * Generate OrderBy type statements
514
694
  */
515
- function addOrderByTypes(sourceFile, tables) {
516
- addSectionComment(sourceFile, 'OrderBy Types');
695
+ function generateOrderByTypes(tables) {
696
+ const statements = [];
517
697
  for (const table of tables) {
518
- // Use getOrderByTypeName which respects table.inflection.orderByType
519
698
  const enumName = getOrderByTypeName(table);
520
- sourceFile.addTypeAlias(createTypeAlias(enumName, buildOrderByUnion(table)));
699
+ const values = buildOrderByValues(table);
700
+ const unionType = createStringLiteralUnion(values);
701
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(enumName), null, unionType);
702
+ statements.push(t.exportNamedDeclaration(typeAlias));
703
+ }
704
+ if (statements.length > 0) {
705
+ addSectionComment(statements, 'OrderBy Types');
521
706
  }
707
+ return statements;
522
708
  }
523
709
  // ============================================================================
524
710
  // CRUD Input Types Generator (AST-based)
@@ -541,27 +727,30 @@ function buildCreateDataFields(table) {
541
727
  return fields;
542
728
  }
543
729
  /**
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.
730
+ * Build Create input interface as AST
549
731
  */
550
732
  function buildCreateInputInterface(table) {
551
733
  const { typeName, singularName } = getTableNames(table);
552
734
  const fields = buildCreateDataFields(table);
553
- const lines = [
554
- `export interface Create${typeName}Input {`,
555
- ` clientMutationId?: string;`,
556
- ` ${singularName}: {`,
735
+ // Build the nested object type for the entity data
736
+ const nestedProps = fields.map((field) => {
737
+ const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(parseTypeString(field.type)));
738
+ prop.optional = field.optional;
739
+ return prop;
740
+ });
741
+ const nestedObjectType = t.tsTypeLiteral(nestedProps);
742
+ // Build the main interface properties
743
+ const mainProps = [
744
+ (() => {
745
+ const prop = t.tsPropertySignature(t.identifier('clientMutationId'), t.tsTypeAnnotation(t.tsStringKeyword()));
746
+ prop.optional = true;
747
+ return prop;
748
+ })(),
749
+ t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(nestedObjectType)),
557
750
  ];
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');
751
+ const body = t.tsInterfaceBody(mainProps);
752
+ const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(`Create${typeName}Input`), null, null, body);
753
+ return t.exportNamedDeclaration(interfaceDecl);
565
754
  }
566
755
  /**
567
756
  * Build Patch type properties
@@ -584,35 +773,41 @@ function buildPatchProperties(table) {
584
773
  return properties;
585
774
  }
586
775
  /**
587
- * Add CRUD input types for a table
776
+ * Generate CRUD input type statements for a table
588
777
  */
589
- function addCrudInputTypes(sourceFile, table) {
778
+ function generateCrudInputTypes(table) {
779
+ const statements = [];
590
780
  const { typeName } = getTableNames(table);
591
781
  const patchName = `${typeName}Patch`;
592
- // Create input - build as raw statement due to nested object type formatting
593
- sourceFile.addStatements(buildCreateInputInterface(table));
782
+ // Create input
783
+ statements.push(buildCreateInputInterface(table));
594
784
  // Patch interface
595
- sourceFile.addInterface(createInterface(patchName, buildPatchProperties(table)));
785
+ statements.push(createExportedInterface(patchName, buildPatchProperties(table)));
596
786
  // Update input
597
- sourceFile.addInterface(createInterface(`Update${typeName}Input`, [
787
+ statements.push(createExportedInterface(`Update${typeName}Input`, [
598
788
  { name: 'clientMutationId', type: 'string', optional: true },
599
789
  { name: 'id', type: 'string', optional: false },
600
790
  { name: 'patch', type: patchName, optional: false },
601
791
  ]));
602
792
  // Delete input
603
- sourceFile.addInterface(createInterface(`Delete${typeName}Input`, [
793
+ statements.push(createExportedInterface(`Delete${typeName}Input`, [
604
794
  { name: 'clientMutationId', type: 'string', optional: true },
605
795
  { name: 'id', type: 'string', optional: false },
606
796
  ]));
797
+ return statements;
607
798
  }
608
799
  /**
609
- * Add all CRUD input types
800
+ * Generate all CRUD input type statements
610
801
  */
611
- function addAllCrudInputTypes(sourceFile, tables) {
612
- addSectionComment(sourceFile, 'CRUD Input Types');
802
+ function generateAllCrudInputTypes(tables) {
803
+ const statements = [];
613
804
  for (const table of tables) {
614
- addCrudInputTypes(sourceFile, table);
805
+ statements.push(...generateCrudInputTypes(table));
806
+ }
807
+ if (statements.length > 0) {
808
+ addSectionComment(statements, 'CRUD Input Types');
615
809
  }
810
+ return statements;
616
811
  }
617
812
  // ============================================================================
618
813
  // Custom Input Types Generator (AST-based)
@@ -640,7 +835,7 @@ export function collectInputTypeNames(operations) {
640
835
  }
641
836
  /**
642
837
  * 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
838
+ * These are generated by generateAllCrudInputTypes, so we don't need to regenerate them
644
839
  */
645
840
  function buildTableCrudTypeNames(tables) {
646
841
  const crudTypes = new Set();
@@ -655,10 +850,10 @@ function buildTableCrudTypeNames(tables) {
655
850
  return crudTypes;
656
851
  }
657
852
  /**
658
- * Add custom input types from TypeRegistry
853
+ * Generate custom input type statements from TypeRegistry
659
854
  */
660
- function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrudTypes) {
661
- addSectionComment(sourceFile, 'Custom Input Types (from schema)');
855
+ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes) {
856
+ const statements = [];
662
857
  const generatedTypes = new Set();
663
858
  const typesToGenerate = new Set(Array.from(usedInputTypes));
664
859
  // Filter out types we've already generated (exact matches for table CRUD types only)
@@ -681,8 +876,11 @@ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrud
681
876
  generatedTypes.add(typeName);
682
877
  const typeInfo = typeRegistry.get(typeName);
683
878
  if (!typeInfo) {
684
- sourceFile.addStatements(`// Type '${typeName}' not found in schema`);
685
- sourceFile.addTypeAlias(createTypeAlias(typeName, 'Record<string, unknown>'));
879
+ // Add comment for missing type
880
+ const commentStmt = t.emptyStatement();
881
+ addLineComment(commentStmt, ` Type '${typeName}' not found in schema`);
882
+ statements.push(commentStmt);
883
+ statements.push(createExportedTypeAlias(typeName, 'Record<string, unknown>'));
686
884
  continue;
687
885
  }
688
886
  if (typeInfo.kind === 'INPUT_OBJECT' && typeInfo.inputFields) {
@@ -699,17 +897,25 @@ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrud
699
897
  typesToGenerate.add(baseType);
700
898
  }
701
899
  }
702
- sourceFile.addInterface(createInterface(typeName, properties));
900
+ statements.push(createExportedInterface(typeName, properties));
703
901
  }
704
902
  else if (typeInfo.kind === 'ENUM' && typeInfo.enumValues) {
705
- const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
706
- sourceFile.addTypeAlias(createTypeAlias(typeName, values));
903
+ const unionType = createStringLiteralUnion(typeInfo.enumValues);
904
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
905
+ statements.push(t.exportNamedDeclaration(typeAlias));
707
906
  }
708
907
  else {
709
- sourceFile.addStatements(`// Type '${typeName}' is ${typeInfo.kind}`);
710
- sourceFile.addTypeAlias(createTypeAlias(typeName, 'unknown'));
908
+ // Add comment for unsupported type kind
909
+ const commentStmt = t.emptyStatement();
910
+ addLineComment(commentStmt, ` Type '${typeName}' is ${typeInfo.kind}`);
911
+ statements.push(commentStmt);
912
+ statements.push(createExportedTypeAlias(typeName, 'unknown'));
711
913
  }
712
914
  }
915
+ if (statements.length > 0) {
916
+ addSectionComment(statements, 'Custom Input Types (from schema)');
917
+ }
918
+ return statements;
713
919
  }
714
920
  // ============================================================================
715
921
  // Payload/Return Types Generator (AST-based)
@@ -729,10 +935,10 @@ export function collectPayloadTypeNames(operations) {
729
935
  return payloadTypes;
730
936
  }
731
937
  /**
732
- * Add payload/return types
938
+ * Generate payload/return type statements
733
939
  */
734
- function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGeneratedTypes) {
735
- addSectionComment(sourceFile, 'Payload/Return Types (for custom operations)');
940
+ function generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes) {
941
+ const statements = [];
736
942
  const generatedTypes = new Set(alreadyGeneratedTypes);
737
943
  const typesToGenerate = new Set(Array.from(usedPayloadTypes));
738
944
  const skipTypes = new Set([
@@ -790,63 +996,77 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
790
996
  }
791
997
  }
792
998
  }
793
- sourceFile.addInterface(createInterface(typeName, interfaceProps));
794
- // Build Select type (no indentation - ts-morph adds it)
795
- const selectLines = ['{'];
999
+ statements.push(createExportedInterface(typeName, interfaceProps));
1000
+ // Build Select type
1001
+ const selectMembers = [];
796
1002
  for (const field of typeInfo.fields) {
797
1003
  const baseType = getTypeBaseName(field.type);
798
1004
  if (baseType === 'Query' || baseType === 'Mutation')
799
1005
  continue;
800
1006
  const nestedType = baseType ? typeRegistry.get(baseType) : null;
1007
+ let propType;
801
1008
  if (nestedType?.kind === 'OBJECT') {
802
- selectLines.push(`${field.name}?: boolean | { select?: ${baseType}Select };`);
1009
+ propType = t.tsUnionType([
1010
+ t.tsBooleanKeyword(),
1011
+ t.tsTypeLiteral([
1012
+ (() => {
1013
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${baseType}Select`))));
1014
+ p.optional = true;
1015
+ return p;
1016
+ })(),
1017
+ ]),
1018
+ ]);
803
1019
  }
804
1020
  else {
805
- selectLines.push(`${field.name}?: boolean;`);
1021
+ propType = t.tsBooleanKeyword();
806
1022
  }
1023
+ const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(propType));
1024
+ prop.optional = true;
1025
+ selectMembers.push(prop);
807
1026
  }
808
- selectLines.push('}');
809
- sourceFile.addTypeAlias(createTypeAlias(`${typeName}Select`, selectLines.join('\n')));
1027
+ const selectTypeAlias = t.tsTypeAliasDeclaration(t.identifier(`${typeName}Select`), null, t.tsTypeLiteral(selectMembers));
1028
+ statements.push(t.exportNamedDeclaration(selectTypeAlias));
1029
+ }
1030
+ if (statements.length > 0) {
1031
+ addSectionComment(statements, 'Payload/Return Types (for custom operations)');
810
1032
  }
1033
+ return statements;
811
1034
  }
812
1035
  // ============================================================================
813
1036
  // Main Generator (AST-based)
814
1037
  // ============================================================================
815
1038
  /**
816
- * Generate comprehensive input-types.ts file using ts-morph AST
1039
+ * Generate comprehensive input-types.ts file using Babel AST
817
1040
  */
818
1041
  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');
1042
+ const statements = [];
823
1043
  // 1. Scalar filter types
824
- addScalarFilterTypes(sourceFile);
1044
+ statements.push(...generateScalarFilterTypes());
825
1045
  // 2. Enum types used by table fields
826
1046
  if (tables && tables.length > 0) {
827
1047
  const enumTypes = collectEnumTypesFromTables(tables, typeRegistry);
828
- addEnumTypes(sourceFile, typeRegistry, enumTypes);
1048
+ statements.push(...generateEnumTypes(typeRegistry, enumTypes));
829
1049
  }
830
1050
  // 3. Entity and relation types (if tables provided)
831
1051
  if (tables && tables.length > 0) {
832
1052
  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);
1053
+ statements.push(...generateEntityTypes(tables));
1054
+ statements.push(...generateRelationHelperTypes());
1055
+ statements.push(...generateEntityRelationTypes(tables, tableByName));
1056
+ statements.push(...generateEntityWithRelations(tables));
1057
+ statements.push(...generateEntitySelectTypes(tables, tableByName));
838
1058
  // 4. Table filter types
839
- addTableFilterTypes(sourceFile, tables);
1059
+ statements.push(...generateTableFilterTypes(tables));
840
1060
  // 4b. Table condition types (simple equality filter)
841
- addTableConditionTypes(sourceFile, tables);
1061
+ statements.push(...generateTableConditionTypes(tables));
842
1062
  // 5. OrderBy types
843
- addOrderByTypes(sourceFile, tables);
1063
+ statements.push(...generateOrderByTypes(tables));
844
1064
  // 6. CRUD input types
845
- addAllCrudInputTypes(sourceFile, tables);
1065
+ statements.push(...generateAllCrudInputTypes(tables));
846
1066
  }
847
1067
  // 7. Custom input types from TypeRegistry
848
1068
  const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
849
- addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrudTypes);
1069
+ statements.push(...generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes));
850
1070
  // 8. Payload/return types for custom operations
851
1071
  if (usedPayloadTypes && usedPayloadTypes.size > 0) {
852
1072
  const alreadyGeneratedTypes = new Set();
@@ -856,10 +1076,13 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
856
1076
  alreadyGeneratedTypes.add(typeName);
857
1077
  }
858
1078
  }
859
- addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGeneratedTypes);
1079
+ statements.push(...generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes));
860
1080
  }
1081
+ // Generate code with file header
1082
+ const header = getGeneratedFileHeader('GraphQL types for ORM client');
1083
+ const code = generateCode(statements);
861
1084
  return {
862
1085
  fileName: 'input-types.ts',
863
- content: getMinimalFormattedOutput(sourceFile),
1086
+ content: header + '\n' + code,
864
1087
  };
865
1088
  }