@constructive-io/graphql-codegen 2.22.1 → 2.23.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 (99) hide show
  1. package/cli/codegen/barrel.d.ts +5 -1
  2. package/cli/codegen/barrel.js +13 -11
  3. package/cli/codegen/index.d.ts +3 -3
  4. package/cli/codegen/index.js +15 -9
  5. package/cli/codegen/orm/client-generator.js +3 -2
  6. package/cli/codegen/orm/custom-ops-generator.js +17 -4
  7. package/cli/codegen/orm/input-types-generator.js +129 -18
  8. package/cli/codegen/orm/model-generator.js +2 -1
  9. package/cli/codegen/orm/query-builder.d.ts +1 -1
  10. package/cli/codegen/orm/query-builder.js +2 -2
  11. package/cli/codegen/schema-types-generator.js +5 -5
  12. package/cli/codegen/utils.d.ts +6 -1
  13. package/cli/codegen/utils.js +23 -8
  14. package/cli/commands/generate-orm.d.ts +5 -3
  15. package/cli/commands/generate-orm.js +65 -84
  16. package/cli/commands/generate.d.ts +2 -0
  17. package/cli/commands/generate.js +66 -87
  18. package/cli/commands/shared.d.ts +74 -0
  19. package/cli/commands/shared.js +88 -0
  20. package/cli/index.js +75 -45
  21. package/cli/introspect/index.d.ts +8 -5
  22. package/cli/introspect/index.js +19 -7
  23. package/cli/introspect/infer-tables.d.ts +51 -0
  24. package/cli/introspect/infer-tables.js +550 -0
  25. package/cli/introspect/source/endpoint.d.ts +34 -0
  26. package/cli/introspect/source/endpoint.js +35 -0
  27. package/cli/introspect/source/file.d.ts +20 -0
  28. package/cli/introspect/source/file.js +103 -0
  29. package/cli/introspect/source/index.d.ts +48 -0
  30. package/cli/introspect/source/index.js +72 -0
  31. package/cli/introspect/source/types.d.ts +58 -0
  32. package/cli/introspect/source/types.js +27 -0
  33. package/cli/introspect/transform.d.ts +5 -6
  34. package/cli/introspect/transform.js +0 -173
  35. package/cli/watch/cache.d.ts +3 -4
  36. package/cli/watch/cache.js +6 -10
  37. package/cli/watch/poller.d.ts +1 -2
  38. package/cli/watch/poller.js +27 -45
  39. package/cli/watch/types.d.ts +0 -3
  40. package/core/ast.js +4 -4
  41. package/core/query-builder.js +12 -12
  42. package/esm/cli/codegen/barrel.d.ts +5 -1
  43. package/esm/cli/codegen/barrel.js +13 -11
  44. package/esm/cli/codegen/index.d.ts +3 -3
  45. package/esm/cli/codegen/index.js +18 -12
  46. package/esm/cli/codegen/orm/client-generator.js +3 -2
  47. package/esm/cli/codegen/orm/custom-ops-generator.js +18 -5
  48. package/esm/cli/codegen/orm/input-types-generator.js +130 -19
  49. package/esm/cli/codegen/orm/model-generator.js +3 -2
  50. package/esm/cli/codegen/orm/query-builder.d.ts +1 -1
  51. package/esm/cli/codegen/orm/query-builder.js +2 -2
  52. package/esm/cli/codegen/schema-types-generator.js +6 -6
  53. package/esm/cli/codegen/utils.d.ts +6 -1
  54. package/esm/cli/codegen/utils.js +22 -8
  55. package/esm/cli/commands/generate-orm.d.ts +5 -3
  56. package/esm/cli/commands/generate-orm.js +65 -84
  57. package/esm/cli/commands/generate.d.ts +2 -0
  58. package/esm/cli/commands/generate.js +66 -87
  59. package/esm/cli/commands/shared.d.ts +74 -0
  60. package/esm/cli/commands/shared.js +84 -0
  61. package/esm/cli/index.js +76 -46
  62. package/esm/cli/introspect/index.d.ts +8 -5
  63. package/esm/cli/introspect/index.js +10 -3
  64. package/esm/cli/introspect/infer-tables.d.ts +51 -0
  65. package/esm/cli/introspect/infer-tables.js +547 -0
  66. package/esm/cli/introspect/source/endpoint.d.ts +34 -0
  67. package/esm/cli/introspect/source/endpoint.js +31 -0
  68. package/esm/cli/introspect/source/file.d.ts +20 -0
  69. package/esm/cli/introspect/source/file.js +66 -0
  70. package/esm/cli/introspect/source/index.d.ts +48 -0
  71. package/esm/cli/introspect/source/index.js +54 -0
  72. package/esm/cli/introspect/source/types.d.ts +58 -0
  73. package/esm/cli/introspect/source/types.js +23 -0
  74. package/esm/cli/introspect/transform.d.ts +5 -6
  75. package/esm/cli/introspect/transform.js +0 -172
  76. package/esm/cli/watch/cache.d.ts +3 -4
  77. package/esm/cli/watch/cache.js +7 -11
  78. package/esm/cli/watch/poller.d.ts +1 -2
  79. package/esm/cli/watch/poller.js +28 -46
  80. package/esm/cli/watch/types.d.ts +0 -3
  81. package/esm/core/ast.js +4 -4
  82. package/esm/core/query-builder.js +12 -12
  83. package/esm/generators/mutations.js +3 -3
  84. package/esm/generators/select.js +7 -7
  85. package/esm/types/config.d.ts +21 -5
  86. package/esm/types/config.js +2 -1
  87. package/generators/mutations.js +3 -3
  88. package/generators/select.js +7 -7
  89. package/package.json +5 -3
  90. package/types/config.d.ts +21 -5
  91. package/types/config.js +2 -1
  92. package/cli/introspect/fetch-meta.d.ts +0 -31
  93. package/cli/introspect/fetch-meta.js +0 -108
  94. package/cli/introspect/meta-query.d.ts +0 -111
  95. package/cli/introspect/meta-query.js +0 -191
  96. package/esm/cli/introspect/fetch-meta.d.ts +0 -31
  97. package/esm/cli/introspect/fetch-meta.js +0 -104
  98. package/esm/cli/introspect/meta-query.d.ts +0 -111
  99. package/esm/cli/introspect/meta-query.js +0 -188
@@ -1,12 +1,18 @@
1
1
  import { createProject, createSourceFile, getMinimalFormattedOutput, createFileHeader, createInterface, createTypeAlias, addSectionComment, } from '../ts-ast';
2
- import { getTableNames, getFilterTypeName, getOrderByTypeName, isRelationField, } from '../utils';
2
+ import { getTableNames, getFilterTypeName, getConditionTypeName, getOrderByTypeName, isRelationField, } from '../utils';
3
+ import { pluralize } from 'inflekt';
3
4
  import { getTypeBaseName } from '../type-resolver';
4
5
  import { scalarToTsType, scalarToFilterType } from '../scalars';
5
6
  // ============================================================================
6
7
  // Constants
7
8
  // ============================================================================
8
9
  /** Fields excluded from Create/Update inputs (auto-generated or system fields) */
9
- const EXCLUDED_MUTATION_FIELDS = ['id', 'createdAt', 'updatedAt', 'nodeId'];
10
+ const EXCLUDED_MUTATION_FIELDS = [
11
+ 'id',
12
+ 'createdAt',
13
+ 'updatedAt',
14
+ 'nodeId',
15
+ ];
10
16
  // ============================================================================
11
17
  // Type Conversion Utilities
12
18
  // ============================================================================
@@ -53,18 +59,58 @@ function isRequired(typeRef) {
53
59
  }
54
60
  /** Configuration for all scalar filter types - matches PostGraphile's generated filters */
55
61
  const SCALAR_FILTER_CONFIGS = [
56
- { name: 'StringFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'string'] },
57
- { name: 'IntFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
58
- { name: 'FloatFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
62
+ {
63
+ name: 'StringFilter',
64
+ tsType: 'string',
65
+ operators: ['equality', 'distinct', 'inArray', 'comparison', 'string'],
66
+ },
67
+ {
68
+ name: 'IntFilter',
69
+ tsType: 'number',
70
+ operators: ['equality', 'distinct', 'inArray', 'comparison'],
71
+ },
72
+ {
73
+ name: 'FloatFilter',
74
+ tsType: 'number',
75
+ operators: ['equality', 'distinct', 'inArray', 'comparison'],
76
+ },
59
77
  { name: 'BooleanFilter', tsType: 'boolean', operators: ['equality'] },
60
- { name: 'UUIDFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray'] },
61
- { name: 'DatetimeFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
62
- { name: 'DateFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
63
- { name: 'JSONFilter', tsType: 'Record<string, unknown>', operators: ['equality', 'distinct', 'json'] },
64
- { name: 'BigIntFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
65
- { name: 'BigFloatFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
78
+ {
79
+ name: 'UUIDFilter',
80
+ tsType: 'string',
81
+ operators: ['equality', 'distinct', 'inArray'],
82
+ },
83
+ {
84
+ name: 'DatetimeFilter',
85
+ tsType: 'string',
86
+ operators: ['equality', 'distinct', 'inArray', 'comparison'],
87
+ },
88
+ {
89
+ name: 'DateFilter',
90
+ tsType: 'string',
91
+ operators: ['equality', 'distinct', 'inArray', 'comparison'],
92
+ },
93
+ {
94
+ name: 'JSONFilter',
95
+ tsType: 'Record<string, unknown>',
96
+ operators: ['equality', 'distinct', 'json'],
97
+ },
98
+ {
99
+ name: 'BigIntFilter',
100
+ tsType: 'string',
101
+ operators: ['equality', 'distinct', 'inArray', 'comparison'],
102
+ },
103
+ {
104
+ name: 'BigFloatFilter',
105
+ tsType: 'string',
106
+ operators: ['equality', 'distinct', 'inArray', 'comparison'],
107
+ },
66
108
  { name: 'BitStringFilter', tsType: 'string', operators: ['equality'] },
67
- { name: 'InternetAddressFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'] },
109
+ {
110
+ name: 'InternetAddressFilter',
111
+ tsType: 'string',
112
+ operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'],
113
+ },
68
114
  { name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
69
115
  ];
70
116
  /**
@@ -224,7 +270,15 @@ function getRelatedTypeName(tableName, tableByName) {
224
270
  }
225
271
  function getRelatedOrderByName(tableName, tableByName) {
226
272
  const relatedTable = tableByName.get(tableName);
227
- return relatedTable ? getOrderByTypeName(relatedTable) : `${tableName}sOrderBy`;
273
+ if (relatedTable) {
274
+ return getOrderByTypeName(relatedTable);
275
+ }
276
+ // For ManyToMany connection types, don't pluralize - just append OrderBy
277
+ // These types already have a fixed suffix pattern like "UserUsersByFooManyToMany"
278
+ if (tableName.endsWith('ManyToMany')) {
279
+ return `${tableName}OrderBy`;
280
+ }
281
+ return `${pluralize(tableName)}OrderBy`;
228
282
  }
229
283
  function getRelatedFilterName(tableName, tableByName) {
230
284
  const relatedTable = tableByName.get(tableName);
@@ -405,6 +459,39 @@ function addTableFilterTypes(sourceFile, tables) {
405
459
  }
406
460
  }
407
461
  // ============================================================================
462
+ // Condition Types Generator (AST-based)
463
+ // ============================================================================
464
+ /**
465
+ * Build properties for a table condition interface
466
+ * Condition types are simpler than Filter types - they use direct value equality
467
+ */
468
+ function buildTableConditionProperties(table) {
469
+ const properties = [];
470
+ for (const field of table.fields) {
471
+ const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
472
+ if (isRelationField(field.name, table))
473
+ continue;
474
+ // Condition types use the raw scalar type (nullable)
475
+ const tsType = scalarToTsType(fieldType, { unknownScalar: 'unknown' });
476
+ properties.push({
477
+ name: field.name,
478
+ type: `${tsType} | null`,
479
+ optional: true,
480
+ });
481
+ }
482
+ return properties;
483
+ }
484
+ /**
485
+ * Add table condition types
486
+ */
487
+ function addTableConditionTypes(sourceFile, tables) {
488
+ addSectionComment(sourceFile, 'Table Condition Types');
489
+ for (const table of tables) {
490
+ const conditionName = getConditionTypeName(table);
491
+ sourceFile.addInterface(createInterface(conditionName, buildTableConditionProperties(table)));
492
+ }
493
+ }
494
+ // ============================================================================
408
495
  // OrderBy Types Generator (AST-based)
409
496
  // ============================================================================
410
497
  /**
@@ -488,7 +575,11 @@ function buildPatchProperties(table) {
488
575
  continue;
489
576
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
490
577
  const tsType = scalarToInputTs(fieldType);
491
- properties.push({ name: field.name, type: `${tsType} | null`, optional: true });
578
+ properties.push({
579
+ name: field.name,
580
+ type: `${tsType} | null`,
581
+ optional: true,
582
+ });
492
583
  }
493
584
  return properties;
494
585
  }
@@ -602,7 +693,9 @@ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrud
602
693
  properties.push({ name: field.name, type: tsType, optional });
603
694
  // Follow nested Input types
604
695
  const baseType = getTypeBaseName(field.type);
605
- if (baseType && baseType.endsWith('Input') && !generatedTypes.has(baseType)) {
696
+ if (baseType &&
697
+ baseType.endsWith('Input') &&
698
+ !generatedTypes.has(baseType)) {
606
699
  typesToGenerate.add(baseType);
607
700
  }
608
701
  }
@@ -628,7 +721,8 @@ export function collectPayloadTypeNames(operations) {
628
721
  const payloadTypes = new Set();
629
722
  for (const op of operations) {
630
723
  const baseName = getTypeBaseName(op.returnType);
631
- if (baseName && (baseName.endsWith('Payload') || !baseName.endsWith('Connection'))) {
724
+ if (baseName &&
725
+ (baseName.endsWith('Payload') || !baseName.endsWith('Connection'))) {
632
726
  payloadTypes.add(baseName);
633
727
  }
634
728
  }
@@ -642,8 +736,21 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
642
736
  const generatedTypes = new Set(alreadyGeneratedTypes);
643
737
  const typesToGenerate = new Set(Array.from(usedPayloadTypes));
644
738
  const skipTypes = new Set([
645
- 'String', 'Int', 'Float', 'Boolean', 'ID', 'UUID', 'Datetime', 'Date',
646
- 'Time', 'JSON', 'BigInt', 'BigFloat', 'Cursor', 'Query', 'Mutation',
739
+ 'String',
740
+ 'Int',
741
+ 'Float',
742
+ 'Boolean',
743
+ 'ID',
744
+ 'UUID',
745
+ 'Datetime',
746
+ 'Date',
747
+ 'Time',
748
+ 'JSON',
749
+ 'BigInt',
750
+ 'BigFloat',
751
+ 'Cursor',
752
+ 'Query',
753
+ 'Mutation',
647
754
  ]);
648
755
  // Process all types - no artificial limit
649
756
  while (typesToGenerate.size > 0) {
@@ -674,7 +781,9 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
674
781
  optional: isNullable,
675
782
  });
676
783
  // Follow nested OBJECT types
677
- if (baseType && !generatedTypes.has(baseType) && !skipTypes.has(baseType)) {
784
+ if (baseType &&
785
+ !generatedTypes.has(baseType) &&
786
+ !skipTypes.has(baseType)) {
678
787
  const nestedType = typeRegistry.get(baseType);
679
788
  if (nestedType?.kind === 'OBJECT') {
680
789
  typesToGenerate.add(baseType);
@@ -728,6 +837,8 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
728
837
  addEntitySelectTypes(sourceFile, tables, tableByName);
729
838
  // 4. Table filter types
730
839
  addTableFilterTypes(sourceFile, tables);
840
+ // 4b. Table condition types (simple equality filter)
841
+ addTableConditionTypes(sourceFile, tables);
731
842
  // 5. OrderBy types
732
843
  addOrderByTypes(sourceFile, tables);
733
844
  // 6. CRUD input types
@@ -1,5 +1,5 @@
1
1
  import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, } from '../ts-ast';
2
- import { getTableNames, getOrderByTypeName, getFilterTypeName, lcFirst } from '../utils';
2
+ import { getTableNames, getOrderByTypeName, getFilterTypeName, lcFirst, } from '../utils';
3
3
  /**
4
4
  * Generate a model class file for a table
5
5
  */
@@ -120,7 +120,8 @@ export function generateModelFile(table, _useSharedTypes) {
120
120
  before: args?.before,
121
121
  offset: args?.offset,
122
122
  },
123
- '${whereTypeName}'
123
+ '${whereTypeName}',
124
+ '${orderByTypeName}'
124
125
  );
125
126
  return new QueryBuilder({
126
127
  client: this.client,
@@ -105,7 +105,7 @@ export declare function buildFindManyDocument(operationName: string, queryField:
105
105
  after?: string;
106
106
  before?: string;
107
107
  offset?: number;
108
- }, filterTypeName: string): {
108
+ }, filterTypeName: string, orderByTypeName: string): {
109
109
  document: string;
110
110
  variables: Record<string, unknown>;
111
111
  };
@@ -164,7 +164,7 @@ export function buildSelections(select, fieldMeta) {
164
164
  /**
165
165
  * Build a findMany query document
166
166
  */
167
- export function buildFindManyDocument(operationName, queryField, select, args, filterTypeName) {
167
+ export function buildFindManyDocument(operationName, queryField, select, args, filterTypeName, orderByTypeName) {
168
168
  const selections = select ? buildSelections(select) : 'id';
169
169
  // Build variable definitions and query arguments
170
170
  const varDefs = [];
@@ -176,7 +176,7 @@ export function buildFindManyDocument(operationName, queryField, select, args, f
176
176
  variables.where = args.where;
177
177
  }
178
178
  if (args.orderBy && args.orderBy.length > 0) {
179
- varDefs.push(`$orderBy: [${operationName}OrderBy!]`);
179
+ varDefs.push(`$orderBy: [${orderByTypeName}!]`);
180
180
  queryArgs.push('orderBy: $orderBy');
181
181
  variables.orderBy = args.orderBy;
182
182
  }
@@ -1,6 +1,6 @@
1
1
  import { createProject, createSourceFile, getMinimalFormattedOutput, createFileHeader, createInterface, createTypeAlias, addSectionComment, } from './ts-ast';
2
2
  import { getTypeBaseName } from './type-resolver';
3
- import { scalarToTsType, SCALAR_NAMES, BASE_FILTER_TYPE_NAMES } from './scalars';
3
+ import { scalarToTsType, SCALAR_NAMES, BASE_FILTER_TYPE_NAMES, } from './scalars';
4
4
  // ============================================================================
5
5
  // Constants
6
6
  // ============================================================================
@@ -26,13 +26,13 @@ const SKIP_TYPES = new Set([
26
26
  /**
27
27
  * Type name patterns to skip (regex patterns)
28
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.
29
+ * Note: We intentionally DO NOT skip Connection, Edge, Filter, Patch, Condition,
30
+ * or OrderBy types because they may be referenced by custom operations.
31
+ * Previously Condition and OrderBy were skipped but they ARE needed for
32
+ * custom queries like `schemata`, `apiSchemata`, etc.
32
33
  */
33
34
  const SKIP_TYPE_PATTERNS = [
34
- /Condition$/, // e.g., UserCondition (filter conditions are separate)
35
- /OrderBy$/, // e.g., UsersOrderBy (ordering is handled separately)
35
+ // Currently no patterns are skipped - all types may be needed by custom operations
36
36
  ];
37
37
  // ============================================================================
38
38
  // Type Conversion Utilities
@@ -101,9 +101,14 @@ export declare function getDeleteMutationName(table: CleanTable): string;
101
101
  export declare function getFilterTypeName(table: CleanTable): string;
102
102
  /**
103
103
  * Get PostGraphile OrderBy enum type name
104
- * e.g., "CarsOrderBy"
104
+ * e.g., "CarsOrderBy", "AddressesOrderBy"
105
105
  */
106
106
  export declare function getOrderByTypeName(table: CleanTable): string;
107
+ /**
108
+ * Get PostGraphile Condition type name (simple equality filter)
109
+ * e.g., "CarCondition", "AddressCondition"
110
+ */
111
+ export declare function getConditionTypeName(table: CleanTable): string;
107
112
  /**
108
113
  * Get PostGraphile create input type name
109
114
  * e.g., "CreateCarInput"
@@ -1,4 +1,5 @@
1
1
  import { scalarToTsType, scalarToFilterType } from './scalars';
2
+ import { pluralize } from 'inflekt';
2
3
  // ============================================================================
3
4
  // String manipulation
4
5
  // ============================================================================
@@ -36,7 +37,9 @@ export function toScreamingSnake(str) {
36
37
  export function getTableNames(table) {
37
38
  const typeName = table.name;
38
39
  const singularName = table.inflection?.tableFieldName || lcFirst(typeName);
39
- const pluralName = table.query?.all || table.inflection?.allRows || singularName + 's';
40
+ const pluralName = table.query?.all ||
41
+ table.inflection?.allRows ||
42
+ lcFirst(pluralize(typeName));
40
43
  const pluralTypeName = ucFirst(pluralName);
41
44
  return {
42
45
  typeName,
@@ -125,13 +128,15 @@ export function getDeleteMutationFileName(table) {
125
128
  * Uses inflection from _meta, falls back to convention
126
129
  */
127
130
  export function getAllRowsQueryName(table) {
128
- return table.query?.all || table.inflection?.allRows || lcFirst(table.name) + 's';
131
+ return (table.query?.all ||
132
+ table.inflection?.allRows ||
133
+ lcFirst(pluralize(table.name)));
129
134
  }
130
135
  /**
131
136
  * Get the GraphQL query name for fetching single row
132
137
  */
133
138
  export function getSingleRowQueryName(table) {
134
- return table.query?.one || table.inflection?.tableFieldName || lcFirst(table.name);
139
+ return (table.query?.one || table.inflection?.tableFieldName || lcFirst(table.name));
135
140
  }
136
141
  /**
137
142
  * Get the GraphQL mutation name for creating
@@ -163,10 +168,17 @@ export function getFilterTypeName(table) {
163
168
  }
164
169
  /**
165
170
  * Get PostGraphile OrderBy enum type name
166
- * e.g., "CarsOrderBy"
171
+ * e.g., "CarsOrderBy", "AddressesOrderBy"
167
172
  */
168
173
  export function getOrderByTypeName(table) {
169
- return table.inflection?.orderByType || `${table.name}sOrderBy`;
174
+ return table.inflection?.orderByType || `${pluralize(table.name)}OrderBy`;
175
+ }
176
+ /**
177
+ * Get PostGraphile Condition type name (simple equality filter)
178
+ * e.g., "CarCondition", "AddressCondition"
179
+ */
180
+ export function getConditionTypeName(table) {
181
+ return table.inflection?.conditionType || `${table.name}Condition`;
170
182
  }
171
183
  /**
172
184
  * Get PostGraphile create input type name
@@ -254,13 +266,15 @@ export function getPrimaryKeyInfo(table) {
254
266
  const pk = table.constraints?.primaryKey?.[0];
255
267
  if (!pk || pk.fields.length === 0) {
256
268
  // Fallback: try to find 'id' field in table fields
257
- const idField = table.fields.find(f => f.name.toLowerCase() === 'id');
269
+ const idField = table.fields.find((f) => f.name.toLowerCase() === 'id');
258
270
  if (idField) {
259
- return [{
271
+ return [
272
+ {
260
273
  name: idField.name,
261
274
  gqlType: idField.type.gqlType,
262
275
  tsType: fieldTypeToTs(idField.type),
263
- }];
276
+ },
277
+ ];
264
278
  }
265
279
  // Last resort: assume 'id' of type string (UUID)
266
280
  return [{ name: 'id', gqlType: 'UUID', tsType: 'string' }];
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Generate ORM command - generates Prisma-like ORM client
2
+ * Generate ORM command - generates Prisma-like ORM client from GraphQL schema
3
3
  *
4
4
  * This command:
5
- * 1. Fetches _meta query for table-based CRUD operations
6
- * 2. Fetches __schema introspection for custom operations
5
+ * 1. Fetches schema from endpoint or loads from file
6
+ * 2. Infers table metadata from introspection (replaces _meta)
7
7
  * 3. Generates a Prisma-like ORM client with fluent API
8
8
  */
9
9
  export interface GenerateOrmOptions {
@@ -11,6 +11,8 @@ export interface GenerateOrmOptions {
11
11
  config?: string;
12
12
  /** GraphQL endpoint URL (overrides config) */
13
13
  endpoint?: string;
14
+ /** Path to GraphQL schema file (.graphql) */
15
+ schema?: string;
14
16
  /** Output directory (overrides config) */
15
17
  output?: string;
16
18
  /** Authorization header */
@@ -1,16 +1,14 @@
1
1
  /**
2
- * Generate ORM command - generates Prisma-like ORM client
2
+ * Generate ORM command - generates Prisma-like ORM client from GraphQL schema
3
3
  *
4
4
  * This command:
5
- * 1. Fetches _meta query for table-based CRUD operations
6
- * 2. Fetches __schema introspection for custom operations
5
+ * 1. Fetches schema from endpoint or loads from file
6
+ * 2. Infers table metadata from introspection (replaces _meta)
7
7
  * 3. Generates a Prisma-like ORM client with fluent API
8
8
  */
9
9
  import { resolveConfig } from '../../types/config';
10
- import { fetchMeta, validateEndpoint } from '../introspect/fetch-meta';
11
- import { fetchSchema } from '../introspect/fetch-schema';
12
- import { transformMetaToCleanTables, filterTables, } from '../introspect/transform';
13
- import { transformSchemaToOperations, filterOperations, getTableOperationNames, getCustomOperations, } from '../introspect/transform-schema';
10
+ import { createSchemaSource, validateSourceOptions, } from '../introspect/source';
11
+ import { runCodegenPipeline, validateTablesFound } from './shared';
14
12
  import { findConfigFile, loadConfigFile } from './init';
15
13
  import { writeGeneratedFiles } from './generate';
16
14
  import { generateOrm } from '../codegen/orm';
@@ -31,109 +29,90 @@ export async function generateOrmCommand(options = {}) {
31
29
  const config = configResult.config;
32
30
  // Use ORM output directory if specified, otherwise default
33
31
  const outputDir = options.output || config.orm?.output || './generated/orm';
34
- log(` Endpoint: ${config.endpoint}`);
32
+ // Log source
33
+ if (config.schema) {
34
+ log(` Schema: ${config.schema}`);
35
+ }
36
+ else {
37
+ log(` Endpoint: ${config.endpoint}`);
38
+ }
35
39
  log(` Output: ${outputDir}`);
36
- // 2. Validate endpoint
37
- const endpointValidation = validateEndpoint(config.endpoint);
38
- if (!endpointValidation.valid) {
40
+ // 2. Create schema source
41
+ const sourceValidation = validateSourceOptions({
42
+ endpoint: config.endpoint || undefined,
43
+ schema: config.schema || undefined,
44
+ });
45
+ if (!sourceValidation.valid) {
39
46
  return {
40
47
  success: false,
41
- message: `Invalid endpoint: ${endpointValidation.error}`,
48
+ message: sourceValidation.error,
42
49
  };
43
50
  }
44
- // Build authorization header if provided
45
- const authHeader = options.authorization || config.headers['Authorization'];
46
- // 3. Fetch _meta for table-based operations
47
- log('Fetching schema metadata (_meta)...');
48
- const metaResult = await fetchMeta({
49
- endpoint: config.endpoint,
50
- authorization: authHeader,
51
+ const source = createSchemaSource({
52
+ endpoint: config.endpoint || undefined,
53
+ schema: config.schema || undefined,
54
+ authorization: options.authorization || config.headers['Authorization'],
51
55
  headers: config.headers,
52
- timeout: 30000,
53
56
  });
54
- if (!metaResult.success) {
57
+ // 3. Run the codegen pipeline
58
+ let pipelineResult;
59
+ try {
60
+ pipelineResult = await runCodegenPipeline({
61
+ source,
62
+ config,
63
+ verbose: options.verbose,
64
+ skipCustomOperations: options.skipCustomOperations,
65
+ });
66
+ }
67
+ catch (err) {
55
68
  return {
56
69
  success: false,
57
- message: `Failed to fetch _meta: ${metaResult.error}`,
70
+ message: `Failed to fetch schema: ${err instanceof Error ? err.message : 'Unknown error'}`,
58
71
  };
59
72
  }
60
- // 4. Transform to CleanTable[]
61
- log('Transforming table schema...');
62
- let tables = transformMetaToCleanTables(metaResult.data);
63
- log(` Found ${tables.length} tables`);
64
- // 5. Filter tables
65
- tables = filterTables(tables, config.tables.include, config.tables.exclude);
66
- log(` After filtering: ${tables.length} tables`);
67
- if (tables.length === 0) {
73
+ const { tables, customOperations, stats } = pipelineResult;
74
+ // 4. Validate tables found
75
+ const tablesValidation = validateTablesFound(tables);
76
+ if (!tablesValidation.valid) {
68
77
  return {
69
78
  success: false,
70
- message: 'No tables found after filtering. Check your include/exclude patterns.',
79
+ message: tablesValidation.error,
71
80
  };
72
81
  }
73
- // Get table operation names for filtering custom operations
74
- const tableOperationNames = getTableOperationNames(tables);
75
- // 6. Fetch __schema for custom operations (unless skipped)
76
- let customQueries = [];
77
- let customMutations = [];
78
- let customOperationsData;
79
- if (!options.skipCustomOperations) {
80
- log('Fetching schema introspection (__schema)...');
81
- const schemaResult = await fetchSchema({
82
- endpoint: config.endpoint,
83
- authorization: authHeader,
84
- headers: config.headers,
85
- timeout: 30000,
86
- });
87
- if (schemaResult.success && schemaResult.data) {
88
- log('Transforming custom operations...');
89
- // Transform to CleanOperation[]
90
- const { queries: allQueries, mutations: allMutations, typeRegistry } = transformSchemaToOperations(schemaResult.data);
91
- log(` Found ${allQueries.length} queries and ${allMutations.length} mutations total`);
92
- // Filter by config include/exclude
93
- const filteredQueries = filterOperations(allQueries, config.queries.include, config.queries.exclude);
94
- const filteredMutations = filterOperations(allMutations, config.mutations.include, config.mutations.exclude);
95
- log(` After config filtering: ${filteredQueries.length} queries, ${filteredMutations.length} mutations`);
96
- // Remove table operations (already handled by table generators)
97
- const customQueriesOps = getCustomOperations(filteredQueries, tableOperationNames);
98
- const customMutationsOps = getCustomOperations(filteredMutations, tableOperationNames);
99
- log(` Custom operations: ${customQueriesOps.length} queries, ${customMutationsOps.length} mutations`);
100
- customQueries = customQueriesOps.map((q) => q.name);
101
- customMutations = customMutationsOps.map((m) => m.name);
102
- customOperationsData = {
103
- queries: customQueriesOps,
104
- mutations: customMutationsOps,
105
- typeRegistry,
106
- };
107
- }
108
- else {
109
- log(` Warning: Could not fetch __schema: ${schemaResult.error}`);
110
- log(' Continuing with table-only generation...');
111
- }
112
- }
113
- // 7. Generate ORM code
82
+ // 5. Generate ORM code
114
83
  console.log('Generating code...');
115
- const { files: generatedFiles, stats } = generateOrm({
84
+ const { files: generatedFiles, stats: genStats } = generateOrm({
116
85
  tables,
117
- customOperations: customOperationsData,
86
+ customOperations: {
87
+ queries: customOperations.queries,
88
+ mutations: customOperations.mutations,
89
+ typeRegistry: customOperations.typeRegistry,
90
+ },
118
91
  config,
119
92
  });
120
- console.log(`Generated ${stats.totalFiles} files`);
121
- log(` ${stats.tables} table models`);
122
- log(` ${stats.customQueries} custom query operations`);
123
- log(` ${stats.customMutations} custom mutation operations`);
93
+ console.log(`Generated ${genStats.totalFiles} files`);
94
+ log(` ${genStats.tables} table models`);
95
+ log(` ${genStats.customQueries} custom query operations`);
96
+ log(` ${genStats.customMutations} custom mutation operations`);
97
+ const customQueries = customOperations.queries.map((q) => q.name);
98
+ const customMutations = customOperations.mutations.map((m) => m.name);
124
99
  if (options.dryRun) {
125
100
  return {
126
101
  success: true,
127
- message: `Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${customQueries.length + customMutations.length} custom operations.`,
102
+ message: `Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${stats.customQueries + stats.customMutations} custom operations.`,
128
103
  tables: tables.map((t) => t.name),
129
104
  customQueries,
130
105
  customMutations,
131
106
  filesWritten: generatedFiles.map((f) => f.path),
132
107
  };
133
108
  }
134
- // 8. Write files
109
+ // 6. Write files
135
110
  log('Writing files...');
136
- const writeResult = await writeGeneratedFiles(generatedFiles, outputDir, ['models', 'query', 'mutation']);
111
+ const writeResult = await writeGeneratedFiles(generatedFiles, outputDir, [
112
+ 'models',
113
+ 'query',
114
+ 'mutation',
115
+ ]);
137
116
  if (!writeResult.success) {
138
117
  return {
139
118
  success: false,
@@ -168,7 +147,8 @@ async function loadConfig(options) {
168
147
  }
169
148
  // Override with CLI options
170
149
  const mergedConfig = {
171
- endpoint: options.endpoint || baseConfig.endpoint || '',
150
+ endpoint: options.endpoint || baseConfig.endpoint,
151
+ schema: options.schema || baseConfig.schema,
172
152
  output: options.output || baseConfig.output,
173
153
  headers: baseConfig.headers,
174
154
  tables: baseConfig.tables,
@@ -180,10 +160,11 @@ async function loadConfig(options) {
180
160
  codegen: baseConfig.codegen,
181
161
  orm: baseConfig.orm,
182
162
  };
183
- if (!mergedConfig.endpoint) {
163
+ // Validate at least one source is provided
164
+ if (!mergedConfig.endpoint && !mergedConfig.schema) {
184
165
  return {
185
166
  success: false,
186
- error: 'No endpoint specified. Use --endpoint or create a config file with "graphql-codegen init".',
167
+ error: 'No source specified. Use --endpoint or --schema, or create a config file with "graphql-codegen init".',
187
168
  };
188
169
  }
189
170
  // Resolve with defaults
@@ -3,6 +3,8 @@ export interface GenerateOptions {
3
3
  config?: string;
4
4
  /** GraphQL endpoint URL (overrides config) */
5
5
  endpoint?: string;
6
+ /** Path to GraphQL schema file (.graphql) */
7
+ schema?: string;
6
8
  /** Output directory (overrides config) */
7
9
  output?: string;
8
10
  /** Authorization header */