@constructive-io/graphql-codegen 2.20.1 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +15 -3
  2. package/cli/codegen/barrel.d.ts +4 -1
  3. package/cli/codegen/barrel.js +18 -12
  4. package/cli/codegen/client.js +33 -0
  5. package/cli/codegen/custom-mutations.d.ts +11 -1
  6. package/cli/codegen/custom-mutations.js +49 -15
  7. package/cli/codegen/custom-queries.d.ts +8 -0
  8. package/cli/codegen/custom-queries.js +82 -47
  9. package/cli/codegen/gql-ast.js +9 -5
  10. package/cli/codegen/index.js +39 -8
  11. package/cli/codegen/mutations.d.ts +14 -4
  12. package/cli/codegen/mutations.js +114 -28
  13. package/cli/codegen/orm/barrel.js +4 -2
  14. package/cli/codegen/orm/index.js +17 -0
  15. package/cli/codegen/orm/input-types-generator.js +83 -29
  16. package/cli/codegen/orm/model-generator.js +6 -4
  17. package/cli/codegen/queries.d.ts +7 -3
  18. package/cli/codegen/queries.js +185 -158
  19. package/cli/codegen/scalars.d.ts +6 -4
  20. package/cli/codegen/scalars.js +17 -9
  21. package/cli/codegen/schema-types-generator.d.ts +26 -0
  22. package/cli/codegen/schema-types-generator.js +365 -0
  23. package/cli/codegen/ts-ast.d.ts +3 -1
  24. package/cli/codegen/ts-ast.js +2 -2
  25. package/cli/codegen/type-resolver.d.ts +52 -6
  26. package/cli/codegen/type-resolver.js +97 -19
  27. package/cli/codegen/types.d.ts +7 -4
  28. package/cli/codegen/types.js +94 -41
  29. package/cli/codegen/utils.d.ts +20 -2
  30. package/cli/codegen/utils.js +32 -7
  31. package/cli/commands/generate-orm.js +5 -5
  32. package/cli/commands/generate.d.ts +4 -1
  33. package/cli/commands/generate.js +27 -8
  34. package/cli/introspect/transform-schema.d.ts +33 -21
  35. package/cli/introspect/transform-schema.js +31 -21
  36. package/esm/cli/codegen/barrel.d.ts +4 -1
  37. package/esm/cli/codegen/barrel.js +18 -12
  38. package/esm/cli/codegen/client.js +33 -0
  39. package/esm/cli/codegen/custom-mutations.d.ts +11 -1
  40. package/esm/cli/codegen/custom-mutations.js +50 -16
  41. package/esm/cli/codegen/custom-queries.d.ts +8 -0
  42. package/esm/cli/codegen/custom-queries.js +83 -48
  43. package/esm/cli/codegen/gql-ast.js +10 -6
  44. package/esm/cli/codegen/index.js +39 -8
  45. package/esm/cli/codegen/mutations.d.ts +14 -4
  46. package/esm/cli/codegen/mutations.js +115 -29
  47. package/esm/cli/codegen/orm/barrel.js +4 -2
  48. package/esm/cli/codegen/orm/index.js +17 -0
  49. package/esm/cli/codegen/orm/input-types-generator.js +83 -29
  50. package/esm/cli/codegen/orm/model-generator.js +7 -5
  51. package/esm/cli/codegen/queries.d.ts +7 -3
  52. package/esm/cli/codegen/queries.js +186 -159
  53. package/esm/cli/codegen/scalars.d.ts +6 -4
  54. package/esm/cli/codegen/scalars.js +16 -8
  55. package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
  56. package/esm/cli/codegen/schema-types-generator.js +362 -0
  57. package/esm/cli/codegen/ts-ast.d.ts +3 -1
  58. package/esm/cli/codegen/ts-ast.js +2 -2
  59. package/esm/cli/codegen/type-resolver.d.ts +52 -6
  60. package/esm/cli/codegen/type-resolver.js +97 -20
  61. package/esm/cli/codegen/types.d.ts +7 -4
  62. package/esm/cli/codegen/types.js +95 -41
  63. package/esm/cli/codegen/utils.d.ts +20 -2
  64. package/esm/cli/codegen/utils.js +31 -7
  65. package/esm/cli/commands/generate-orm.js +5 -5
  66. package/esm/cli/commands/generate.d.ts +4 -1
  67. package/esm/cli/commands/generate.js +27 -8
  68. package/esm/cli/introspect/transform-schema.d.ts +33 -21
  69. package/esm/cli/introspect/transform-schema.js +31 -21
  70. package/esm/types/config.d.ts +16 -1
  71. package/esm/types/config.js +6 -0
  72. package/esm/types/schema.d.ts +2 -0
  73. package/package.json +8 -6
  74. package/types/config.d.ts +16 -1
  75. package/types/config.js +6 -0
  76. package/types/schema.d.ts +2 -0
  77. package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  78. package/__tests__/codegen/input-types-generator.test.js +0 -635
  79. package/cli/codegen/filters.d.ts +0 -27
  80. package/cli/codegen/filters.js +0 -357
  81. package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  82. package/cli/codegen/orm/input-types-generator.test.js +0 -75
  83. package/cli/codegen/orm/select-types.test.d.ts +0 -11
  84. package/cli/codegen/orm/select-types.test.js +0 -22
  85. package/cli/introspect/transform-schema.test.d.ts +0 -1
  86. package/cli/introspect/transform-schema.test.js +0 -67
  87. package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  88. package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
  89. package/esm/cli/codegen/filters.d.ts +0 -27
  90. package/esm/cli/codegen/filters.js +0 -351
  91. package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  92. package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
  93. package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
  94. package/esm/cli/codegen/orm/select-types.test.js +0 -21
  95. package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
  96. package/esm/cli/introspect/transform-schema.test.js +0 -65
@@ -3,10 +3,13 @@
3
3
  */
4
4
  import type { CleanTable } from '../../types/schema';
5
5
  /**
6
- * Generate types.ts content with all entity interfaces and base filter types
6
+ * Options for generating types.ts
7
7
  */
8
- export declare function generateTypesFile(tables: CleanTable[]): string;
8
+ export interface GenerateTypesOptions {
9
+ /** Enum type names that are available from schema-types.ts */
10
+ enumsFromSchemaTypes?: string[];
11
+ }
9
12
  /**
10
- * Generate a minimal entity type (just id and display fields)
13
+ * Generate types.ts content with all entity interfaces and base filter types
11
14
  */
12
- export declare function generateMinimalEntityType(table: CleanTable): string;
15
+ export declare function generateTypesFile(tables: CleanTable[], options?: GenerateTypesOptions): string;
@@ -1,19 +1,103 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateTypesFile = generateTypesFile;
4
- exports.generateMinimalEntityType = generateMinimalEntityType;
5
4
  const ts_ast_1 = require("./ts-ast");
6
5
  const utils_1 = require("./utils");
7
- const filters_1 = require("./filters");
6
+ /** All filter type configurations - scalar and list filters */
7
+ const FILTER_CONFIGS = [
8
+ // Scalar filters
9
+ { name: 'StringFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'string'] },
10
+ { name: 'IntFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
11
+ { name: 'FloatFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
12
+ { name: 'BooleanFilter', tsType: 'boolean', operators: ['equality'] },
13
+ { name: 'UUIDFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray'] },
14
+ { name: 'DatetimeFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
15
+ { name: 'DateFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
16
+ { name: 'JSONFilter', tsType: 'Record<string, unknown>', operators: ['equality', 'distinct', 'json'] },
17
+ { name: 'BigIntFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
18
+ { name: 'BigFloatFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
19
+ { name: 'BitStringFilter', tsType: 'string', operators: ['equality'] },
20
+ { name: 'InternetAddressFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'] },
21
+ { name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
22
+ // List filters
23
+ { name: 'StringListFilter', tsType: 'string[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
24
+ { name: 'IntListFilter', tsType: 'number[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
25
+ { name: 'UUIDListFilter', tsType: 'string[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
26
+ ];
27
+ /** Build filter properties based on operator sets */
28
+ function buildFilterProperties(tsType, operators) {
29
+ const props = [];
30
+ // Equality operators (isNull, equalTo, notEqualTo)
31
+ if (operators.includes('equality')) {
32
+ props.push({ name: 'isNull', type: 'boolean', optional: true }, { name: 'equalTo', type: tsType, optional: true }, { name: 'notEqualTo', type: tsType, optional: true });
33
+ }
34
+ // Distinct operators
35
+ if (operators.includes('distinct')) {
36
+ props.push({ name: 'distinctFrom', type: tsType, optional: true }, { name: 'notDistinctFrom', type: tsType, optional: true });
37
+ }
38
+ // In array operators
39
+ if (operators.includes('inArray')) {
40
+ props.push({ name: 'in', type: `${tsType}[]`, optional: true }, { name: 'notIn', type: `${tsType}[]`, optional: true });
41
+ }
42
+ // Comparison operators (lt, lte, gt, gte)
43
+ if (operators.includes('comparison')) {
44
+ props.push({ name: 'lessThan', type: tsType, optional: true }, { name: 'lessThanOrEqualTo', type: tsType, optional: true }, { name: 'greaterThan', type: tsType, optional: true }, { name: 'greaterThanOrEqualTo', type: tsType, optional: true });
45
+ }
46
+ // String-specific operators
47
+ if (operators.includes('string')) {
48
+ props.push({ name: 'includes', type: 'string', optional: true }, { name: 'notIncludes', type: 'string', optional: true }, { name: 'includesInsensitive', type: 'string', optional: true }, { name: 'notIncludesInsensitive', type: 'string', optional: true }, { name: 'startsWith', type: 'string', optional: true }, { name: 'notStartsWith', type: 'string', optional: true }, { name: 'startsWithInsensitive', type: 'string', optional: true }, { name: 'notStartsWithInsensitive', type: 'string', optional: true }, { name: 'endsWith', type: 'string', optional: true }, { name: 'notEndsWith', type: 'string', optional: true }, { name: 'endsWithInsensitive', type: 'string', optional: true }, { name: 'notEndsWithInsensitive', type: 'string', optional: true }, { name: 'like', type: 'string', optional: true }, { name: 'notLike', type: 'string', optional: true }, { name: 'likeInsensitive', type: 'string', optional: true }, { name: 'notLikeInsensitive', type: 'string', optional: true });
49
+ }
50
+ // JSON-specific operators
51
+ if (operators.includes('json')) {
52
+ props.push({ name: 'contains', type: 'unknown', optional: true }, { name: 'containedBy', type: 'unknown', optional: true }, { name: 'containsKey', type: 'string', optional: true }, { name: 'containsAllKeys', type: 'string[]', optional: true }, { name: 'containsAnyKeys', type: 'string[]', optional: true });
53
+ }
54
+ // Internet address operators
55
+ if (operators.includes('inet')) {
56
+ props.push({ name: 'contains', type: 'string', optional: true }, { name: 'containedBy', type: 'string', optional: true }, { name: 'containsOrContainedBy', type: 'string', optional: true });
57
+ }
58
+ // Full-text search operator
59
+ if (operators.includes('fulltext')) {
60
+ props.push({ name: 'matches', type: 'string', optional: true });
61
+ }
62
+ // List/Array operators (for StringListFilter, IntListFilter, etc.)
63
+ if (operators.includes('listArray')) {
64
+ // Extract base type from array type (e.g., 'string[]' -> 'string')
65
+ const baseType = tsType.replace('[]', '');
66
+ 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 });
67
+ }
68
+ return props;
69
+ }
8
70
  /**
9
71
  * Generate types.ts content with all entity interfaces and base filter types
10
72
  */
11
- function generateTypesFile(tables) {
73
+ function generateTypesFile(tables, options = {}) {
74
+ const { enumsFromSchemaTypes = [] } = options;
75
+ const enumSet = new Set(enumsFromSchemaTypes);
12
76
  const project = (0, ts_ast_1.createProject)();
13
77
  const sourceFile = (0, ts_ast_1.createSourceFile)(project, 'types.ts');
14
78
  // Add file header
15
79
  sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)('Entity types and filter types') + '\n\n');
16
- // Add section comment
80
+ // Collect which enums are actually used by entity fields
81
+ const usedEnums = new Set();
82
+ for (const table of tables) {
83
+ const scalarFields = (0, utils_1.getScalarFields)(table);
84
+ for (const field of scalarFields) {
85
+ // Check if the field's gqlType matches any known enum
86
+ const cleanType = field.type.gqlType.replace(/!/g, '');
87
+ if (enumSet.has(cleanType)) {
88
+ usedEnums.add(cleanType);
89
+ }
90
+ }
91
+ }
92
+ // Add import for enum types from schema-types if any are used
93
+ if (usedEnums.size > 0) {
94
+ sourceFile.addImportDeclaration((0, ts_ast_1.createImport)({
95
+ moduleSpecifier: './schema-types',
96
+ typeOnlyNamedImports: Array.from(usedEnums).sort(),
97
+ }));
98
+ sourceFile.addStatements('');
99
+ }
100
+ // Add section comment for entity types
17
101
  sourceFile.addStatements('// ============================================================================');
18
102
  sourceFile.addStatements('// Entity types');
19
103
  sourceFile.addStatements('// ============================================================================\n');
@@ -26,44 +110,13 @@ function generateTypesFile(tables) {
26
110
  }));
27
111
  sourceFile.addInterface((0, ts_ast_1.createInterface)(table.name, properties));
28
112
  }
29
- // Add section comment for filters
113
+ // Add section comment for filter types
30
114
  sourceFile.addStatements('\n// ============================================================================');
31
- sourceFile.addStatements('// Filter types (shared)');
115
+ sourceFile.addStatements('// Filter types (shared PostGraphile filter interfaces)');
32
116
  sourceFile.addStatements('// ============================================================================\n');
33
- // Add base filter types (using string concat for complex types - acceptable for static definitions)
34
- const filterTypesContent = (0, filters_1.generateBaseFilterTypes)();
35
- // Extract just the interfaces part (skip the header)
36
- const filterInterfaces = filterTypesContent
37
- .split('\n')
38
- .slice(6) // Skip header lines
39
- .join('\n');
40
- sourceFile.addStatements(filterInterfaces);
41
- return (0, ts_ast_1.getFormattedOutput)(sourceFile);
42
- }
43
- /**
44
- * Generate a minimal entity type (just id and display fields)
45
- */
46
- function generateMinimalEntityType(table) {
47
- const project = (0, ts_ast_1.createProject)();
48
- const sourceFile = (0, ts_ast_1.createSourceFile)(project, 'minimal.ts');
49
- const scalarFields = (0, utils_1.getScalarFields)(table);
50
- // Find id and likely display fields
51
- const displayFields = scalarFields.filter((f) => {
52
- const name = f.name.toLowerCase();
53
- return (name === 'id' ||
54
- name === 'name' ||
55
- name === 'title' ||
56
- name === 'label' ||
57
- name === 'email' ||
58
- name.endsWith('name') ||
59
- name.endsWith('title'));
60
- });
61
- // If no display fields found, take first 5 scalar fields
62
- const fieldsToUse = displayFields.length > 0 ? displayFields : scalarFields.slice(0, 5);
63
- const properties = fieldsToUse.map((field) => ({
64
- name: field.name,
65
- type: `${(0, utils_1.fieldTypeToTs)(field.type)} | null`,
66
- }));
67
- sourceFile.addInterface((0, ts_ast_1.createInterface)(`${table.name}Minimal`, properties));
117
+ // Generate all filter types
118
+ for (const { name, tsType, operators } of FILTER_CONFIGS) {
119
+ sourceFile.addInterface((0, ts_ast_1.createInterface)(name, buildFilterProperties(tsType, operators)));
120
+ }
68
121
  return (0, ts_ast_1.getFormattedOutput)(sourceFile);
69
122
  }
@@ -134,8 +134,10 @@ export declare function gqlTypeToTs(gqlType: string, isArray?: boolean): string;
134
134
  export declare function fieldTypeToTs(fieldType: CleanFieldType): string;
135
135
  /**
136
136
  * Get the PostGraphile filter type for a GraphQL scalar
137
+ * @param gqlType - The GraphQL type string (e.g., "String", "UUID")
138
+ * @param isArray - Whether this is an array type
137
139
  */
138
- export declare function getScalarFilterType(gqlType: string): string | null;
140
+ export declare function getScalarFilterType(gqlType: string, isArray?: boolean): string | null;
139
141
  /**
140
142
  * Check if a field is a relation field (not a scalar)
141
143
  */
@@ -145,7 +147,23 @@ export declare function isRelationField(fieldName: string, table: CleanTable): b
145
147
  */
146
148
  export declare function getScalarFields(table: CleanTable): CleanField[];
147
149
  /**
148
- * Get primary key field names
150
+ * Primary key field information
151
+ */
152
+ export interface PrimaryKeyField {
153
+ /** Field name */
154
+ name: string;
155
+ /** GraphQL type (e.g., "UUID", "Int", "String") */
156
+ gqlType: string;
157
+ /** TypeScript type (e.g., "string", "number") */
158
+ tsType: string;
159
+ }
160
+ /**
161
+ * Get primary key field information from table constraints
162
+ * Returns array to support composite primary keys
163
+ */
164
+ export declare function getPrimaryKeyInfo(table: CleanTable): PrimaryKeyField[];
165
+ /**
166
+ * Get primary key field names (convenience wrapper)
149
167
  */
150
168
  export declare function getPrimaryKeyFields(table: CleanTable): string[];
151
169
  /**
@@ -32,6 +32,7 @@ exports.fieldTypeToTs = fieldTypeToTs;
32
32
  exports.getScalarFilterType = getScalarFilterType;
33
33
  exports.isRelationField = isRelationField;
34
34
  exports.getScalarFields = getScalarFields;
35
+ exports.getPrimaryKeyInfo = getPrimaryKeyInfo;
35
36
  exports.getPrimaryKeyFields = getPrimaryKeyFields;
36
37
  exports.getQueryKeyPrefix = getQueryKeyPrefix;
37
38
  exports.getGeneratedFileHeader = getGeneratedFileHeader;
@@ -258,10 +259,12 @@ function fieldTypeToTs(fieldType) {
258
259
  // ============================================================================
259
260
  /**
260
261
  * Get the PostGraphile filter type for a GraphQL scalar
262
+ * @param gqlType - The GraphQL type string (e.g., "String", "UUID")
263
+ * @param isArray - Whether this is an array type
261
264
  */
262
- function getScalarFilterType(gqlType) {
265
+ function getScalarFilterType(gqlType, isArray = false) {
263
266
  const cleanType = gqlType.replace(/!/g, '');
264
- return (0, scalars_1.scalarToFilterType)(cleanType);
267
+ return (0, scalars_1.scalarToFilterType)(cleanType, isArray);
265
268
  }
266
269
  // ============================================================================
267
270
  // Field filtering utilities
@@ -283,13 +286,35 @@ function getScalarFields(table) {
283
286
  return table.fields.filter((f) => !isRelationField(f.name, table));
284
287
  }
285
288
  /**
286
- * Get primary key field names
289
+ * Get primary key field information from table constraints
290
+ * Returns array to support composite primary keys
287
291
  */
288
- function getPrimaryKeyFields(table) {
292
+ function getPrimaryKeyInfo(table) {
289
293
  const pk = table.constraints?.primaryKey?.[0];
290
- if (!pk)
291
- return ['id']; // Default assumption
292
- return pk.fields.map((f) => f.name);
294
+ if (!pk || pk.fields.length === 0) {
295
+ // Fallback: try to find 'id' field in table fields
296
+ const idField = table.fields.find(f => f.name.toLowerCase() === 'id');
297
+ if (idField) {
298
+ return [{
299
+ name: idField.name,
300
+ gqlType: idField.type.gqlType,
301
+ tsType: fieldTypeToTs(idField.type),
302
+ }];
303
+ }
304
+ // Last resort: assume 'id' of type string (UUID)
305
+ return [{ name: 'id', gqlType: 'UUID', tsType: 'string' }];
306
+ }
307
+ return pk.fields.map((f) => ({
308
+ name: f.name,
309
+ gqlType: f.type.gqlType,
310
+ tsType: fieldTypeToTs(f.type),
311
+ }));
312
+ }
313
+ /**
314
+ * Get primary key field names (convenience wrapper)
315
+ */
316
+ function getPrimaryKeyFields(table) {
317
+ return getPrimaryKeyInfo(table).map((pk) => pk.name);
293
318
  }
294
319
  // ============================================================================
295
320
  // Query key generation
@@ -114,16 +114,16 @@ async function generateOrmCommand(options = {}) {
114
114
  }
115
115
  }
116
116
  // 7. Generate ORM code
117
- log('Generating ORM client...');
117
+ console.log('Generating code...');
118
118
  const { files: generatedFiles, stats } = (0, orm_1.generateOrm)({
119
119
  tables,
120
120
  customOperations: customOperationsData,
121
121
  config,
122
122
  });
123
- log(` Generated ${stats.tables} table models`);
124
- log(` Generated ${stats.customQueries} custom query operations`);
125
- log(` Generated ${stats.customMutations} custom mutation operations`);
126
- log(` Total files: ${stats.totalFiles}`);
123
+ console.log(`Generated ${stats.totalFiles} files`);
124
+ log(` ${stats.tables} table models`);
125
+ log(` ${stats.customQueries} custom query operations`);
126
+ log(` ${stats.customMutations} custom mutation operations`);
127
127
  if (options.dryRun) {
128
128
  return {
129
129
  success: true,
@@ -36,4 +36,7 @@ export interface WriteResult {
36
36
  filesWritten?: string[];
37
37
  errors?: string[];
38
38
  }
39
- export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: string, subdirs: string[]): Promise<WriteResult>;
39
+ export interface WriteOptions {
40
+ showProgress?: boolean;
41
+ }
42
+ export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: string, subdirs: string[], options?: WriteOptions): Promise<WriteResult>;
@@ -149,17 +149,17 @@ async function generateCommand(options = {}) {
149
149
  }
150
150
  }
151
151
  // 7. Generate code
152
- log('Generating code...');
152
+ console.log('Generating code...');
153
153
  const { files: generatedFiles, stats } = (0, codegen_1.generate)({
154
154
  tables,
155
155
  customOperations: customOperationsData,
156
156
  config,
157
157
  });
158
- log(` Generated ${stats.queryHooks} table query hooks`);
159
- log(` Generated ${stats.mutationHooks} table mutation hooks`);
160
- log(` Generated ${stats.customQueryHooks} custom query hooks`);
161
- log(` Generated ${stats.customMutationHooks} custom mutation hooks`);
162
- log(` Total files: ${stats.totalFiles}`);
158
+ console.log(`Generated ${stats.totalFiles} files`);
159
+ log(` ${stats.queryHooks} table query hooks`);
160
+ log(` ${stats.mutationHooks} table mutation hooks`);
161
+ log(` ${stats.customQueryHooks} custom query hooks`);
162
+ log(` ${stats.customMutationHooks} custom mutation hooks`);
163
163
  if (options.dryRun) {
164
164
  return {
165
165
  success: true,
@@ -228,9 +228,12 @@ async function loadConfig(options) {
228
228
  const config = (0, config_1.resolveConfig)(mergedConfig);
229
229
  return { success: true, config };
230
230
  }
231
- async function writeGeneratedFiles(files, outputDir, subdirs) {
231
+ async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
232
+ const { showProgress = true } = options;
232
233
  const errors = [];
233
234
  const written = [];
235
+ const total = files.length;
236
+ const isTTY = process.stdout.isTTY;
234
237
  // Ensure output directory exists
235
238
  try {
236
239
  fs.mkdirSync(outputDir, { recursive: true });
@@ -256,8 +259,20 @@ async function writeGeneratedFiles(files, outputDir, subdirs) {
256
259
  if (errors.length > 0) {
257
260
  return { success: false, errors };
258
261
  }
259
- for (const file of files) {
262
+ for (let i = 0; i < files.length; i++) {
263
+ const file = files[i];
260
264
  const filePath = path.join(outputDir, file.path);
265
+ // Show progress
266
+ if (showProgress) {
267
+ const progress = Math.round(((i + 1) / total) * 100);
268
+ if (isTTY) {
269
+ process.stdout.write(`\rWriting files: ${i + 1}/${total} (${progress}%)`);
270
+ }
271
+ else if (i % 100 === 0 || i === total - 1) {
272
+ // Non-TTY: periodic updates for CI/CD
273
+ console.log(`Writing files: ${i + 1}/${total}`);
274
+ }
275
+ }
261
276
  // Ensure parent directory exists
262
277
  const parentDir = path.dirname(filePath);
263
278
  try {
@@ -277,6 +292,10 @@ async function writeGeneratedFiles(files, outputDir, subdirs) {
277
292
  errors.push(`Failed to write ${filePath}: ${message}`);
278
293
  }
279
294
  }
295
+ // Clear progress line
296
+ if (showProgress && isTTY) {
297
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
298
+ }
280
299
  return {
281
300
  success: errors.length === 0,
282
301
  filesWritten: written,
@@ -30,13 +30,29 @@ export declare function transformSchemaToOperations(response: IntrospectionQuery
30
30
  * Uses glob-like patterns (supports * wildcard)
31
31
  */
32
32
  export declare function filterOperations(operations: CleanOperation[], include?: string[], exclude?: string[]): CleanOperation[];
33
+ /**
34
+ * Result type for table operation names lookup
35
+ */
36
+ export interface TableOperationNames {
37
+ queries: Set<string>;
38
+ mutations: Set<string>;
39
+ }
33
40
  /**
34
41
  * Get the set of table-related operation names from tables
35
42
  * Used to identify which operations are already covered by table generators
36
43
  *
37
- * This includes:
38
- * - Basic CRUD: all, one, create, update, delete
39
- * - PostGraphile alternate mutations: updateXByY, deleteXByY (for unique constraints)
44
+ * IMPORTANT: This uses EXACT matches only from _meta.query fields.
45
+ * Any operation not explicitly listed in _meta will flow through as a
46
+ * custom operation, ensuring 100% coverage of the schema.
47
+ *
48
+ * Table operations (generated by table generators):
49
+ * - Queries: all (list), one (single by PK)
50
+ * - Mutations: create, update (by PK), delete (by PK)
51
+ *
52
+ * Custom operations (generated by custom operation generators):
53
+ * - Unique constraint lookups: *ByUsername, *ByEmail, etc.
54
+ * - Unique constraint mutations: update*By*, delete*By*
55
+ * - True custom operations: login, register, bootstrapUser, etc.
40
56
  */
41
57
  export declare function getTableOperationNames(tables: Array<{
42
58
  name: string;
@@ -47,28 +63,24 @@ export declare function getTableOperationNames(tables: Array<{
47
63
  update: string | null;
48
64
  delete: string | null;
49
65
  };
50
- inflection?: {
51
- tableType: string;
52
- };
53
- }>): {
54
- queries: Set<string>;
55
- mutations: Set<string>;
56
- tableTypePatterns: RegExp[];
57
- };
66
+ }>): TableOperationNames;
58
67
  /**
59
68
  * Check if an operation is a table operation (already handled by table generators)
69
+ *
70
+ * Uses EXACT match only - no pattern matching. This ensures:
71
+ * 1. Only operations explicitly in _meta.query are treated as table operations
72
+ * 2. All other operations (including update*By*, delete*By*) become custom operations
73
+ * 3. 100% schema coverage is guaranteed
60
74
  */
61
- export declare function isTableOperation(operation: CleanOperation, tableOperationNames: {
62
- queries: Set<string>;
63
- mutations: Set<string>;
64
- tableTypePatterns: RegExp[];
65
- }): boolean;
75
+ export declare function isTableOperation(operation: CleanOperation, tableOperationNames: TableOperationNames): boolean;
66
76
  /**
67
77
  * Get only custom operations (not covered by table generators)
78
+ *
79
+ * This returns ALL operations that are not exact matches for table CRUD operations.
80
+ * Includes:
81
+ * - Unique constraint queries (*ByUsername, *ByEmail, etc.)
82
+ * - Unique constraint mutations (update*By*, delete*By*)
83
+ * - True custom operations (login, register, bootstrapUser, etc.)
68
84
  */
69
- export declare function getCustomOperations(operations: CleanOperation[], tableOperationNames: {
70
- queries: Set<string>;
71
- mutations: Set<string>;
72
- tableTypePatterns: RegExp[];
73
- }): CleanOperation[];
85
+ export declare function getCustomOperations(operations: CleanOperation[], tableOperationNames: TableOperationNames): CleanOperation[];
74
86
  export { unwrapType, getBaseTypeName, isNonNull };
@@ -38,6 +38,10 @@ function buildTypeRegistry(types) {
38
38
  if (type.kind === 'ENUM' && type.enumValues) {
39
39
  resolvedType.enumValues = type.enumValues.map((ev) => ev.name);
40
40
  }
41
+ // Resolve possible types for UNION types (no circular refs for names)
42
+ if (type.kind === 'UNION' && type.possibleTypes) {
43
+ resolvedType.possibleTypes = type.possibleTypes.map((pt) => pt.name);
44
+ }
41
45
  registry.set(type.name, resolvedType);
42
46
  }
43
47
  // Second pass: Resolve fields (now that all types exist in registry)
@@ -212,57 +216,63 @@ function matchesPatterns(name, patterns) {
212
216
  return name === pattern;
213
217
  });
214
218
  }
215
- // ============================================================================
216
- // Utility Functions
217
- // ============================================================================
218
219
  /**
219
220
  * Get the set of table-related operation names from tables
220
221
  * Used to identify which operations are already covered by table generators
221
222
  *
222
- * This includes:
223
- * - Basic CRUD: all, one, create, update, delete
224
- * - PostGraphile alternate mutations: updateXByY, deleteXByY (for unique constraints)
223
+ * IMPORTANT: This uses EXACT matches only from _meta.query fields.
224
+ * Any operation not explicitly listed in _meta will flow through as a
225
+ * custom operation, ensuring 100% coverage of the schema.
226
+ *
227
+ * Table operations (generated by table generators):
228
+ * - Queries: all (list), one (single by PK)
229
+ * - Mutations: create, update (by PK), delete (by PK)
230
+ *
231
+ * Custom operations (generated by custom operation generators):
232
+ * - Unique constraint lookups: *ByUsername, *ByEmail, etc.
233
+ * - Unique constraint mutations: update*By*, delete*By*
234
+ * - True custom operations: login, register, bootstrapUser, etc.
225
235
  */
226
236
  function getTableOperationNames(tables) {
227
237
  const queries = new Set();
228
238
  const mutations = new Set();
229
- const tableTypePatterns = [];
230
239
  for (const table of tables) {
231
240
  if (table.query) {
241
+ // Add exact query names from _meta
232
242
  queries.add(table.query.all);
233
243
  queries.add(table.query.one);
244
+ // Add exact mutation names from _meta
234
245
  mutations.add(table.query.create);
235
246
  if (table.query.update)
236
247
  mutations.add(table.query.update);
237
248
  if (table.query.delete)
238
249
  mutations.add(table.query.delete);
239
250
  }
240
- // Create patterns to match alternate CRUD mutations (updateXByY, deleteXByY)
241
- if (table.inflection?.tableType) {
242
- const typeName = table.inflection.tableType;
243
- // Match: update{TypeName}By*, delete{TypeName}By*
244
- tableTypePatterns.push(new RegExp(`^update${typeName}By`, 'i'));
245
- tableTypePatterns.push(new RegExp(`^delete${typeName}By`, 'i'));
246
- }
247
251
  }
248
- return { queries, mutations, tableTypePatterns };
252
+ return { queries, mutations };
249
253
  }
250
254
  /**
251
255
  * Check if an operation is a table operation (already handled by table generators)
256
+ *
257
+ * Uses EXACT match only - no pattern matching. This ensures:
258
+ * 1. Only operations explicitly in _meta.query are treated as table operations
259
+ * 2. All other operations (including update*By*, delete*By*) become custom operations
260
+ * 3. 100% schema coverage is guaranteed
252
261
  */
253
262
  function isTableOperation(operation, tableOperationNames) {
254
263
  if (operation.kind === 'query') {
255
264
  return tableOperationNames.queries.has(operation.name);
256
265
  }
257
- // Check exact match first
258
- if (tableOperationNames.mutations.has(operation.name)) {
259
- return true;
260
- }
261
- // Check pattern match for alternate CRUD mutations (updateXByY, deleteXByY)
262
- return tableOperationNames.tableTypePatterns.some((pattern) => pattern.test(operation.name));
266
+ return tableOperationNames.mutations.has(operation.name);
263
267
  }
264
268
  /**
265
269
  * Get only custom operations (not covered by table generators)
270
+ *
271
+ * This returns ALL operations that are not exact matches for table CRUD operations.
272
+ * Includes:
273
+ * - Unique constraint queries (*ByUsername, *ByEmail, etc.)
274
+ * - Unique constraint mutations (update*By*, delete*By*)
275
+ * - True custom operations (login, register, bootstrapUser, etc.)
266
276
  */
267
277
  function getCustomOperations(operations, tableOperationNames) {
268
278
  return operations.filter((op) => !isTableOperation(op, tableOperationNames));
@@ -15,8 +15,11 @@ export declare function generateQueriesBarrel(tables: CleanTable[]): string;
15
15
  export declare function generateMutationsBarrel(tables: CleanTable[]): string;
16
16
  /**
17
17
  * Generate the main index.ts barrel file
18
+ *
19
+ * @param tables - The tables to include in the SDK
20
+ * @param hasSchemaTypes - Whether schema-types.ts was generated
18
21
  */
19
- export declare function generateMainBarrel(tables: CleanTable[]): string;
22
+ export declare function generateMainBarrel(tables: CleanTable[], hasSchemaTypes?: boolean): string;
20
23
  /**
21
24
  * Generate queries barrel including custom query operations
22
25
  */
@@ -5,10 +5,7 @@ import { getOperationHookName } from './type-resolver';
5
5
  * Generate the queries/index.ts barrel file
6
6
  */
7
7
  export function generateQueriesBarrel(tables) {
8
- const lines = [
9
- createFileHeader('Query hooks barrel export'),
10
- '',
11
- ];
8
+ const lines = [createFileHeader('Query hooks barrel export'), ''];
12
9
  // Export all query hooks
13
10
  for (const table of tables) {
14
11
  const listHookName = getListQueryHookName(table);
@@ -44,31 +41,40 @@ export function generateMutationsBarrel(tables) {
44
41
  }
45
42
  /**
46
43
  * Generate the main index.ts barrel file
44
+ *
45
+ * @param tables - The tables to include in the SDK
46
+ * @param hasSchemaTypes - Whether schema-types.ts was generated
47
47
  */
48
- export function generateMainBarrel(tables) {
48
+ export function generateMainBarrel(tables, hasSchemaTypes = false) {
49
49
  const tableNames = tables.map((t) => t.name).join(', ');
50
+ const schemaTypesExport = hasSchemaTypes
51
+ ? `
52
+ // Schema types (input, payload, enum types)
53
+ export * from './schema-types';
54
+ `
55
+ : '';
50
56
  return `/**
51
57
  * Auto-generated GraphQL SDK
52
58
  * @generated by @constructive-io/graphql-codegen
53
- *
59
+ *
54
60
  * Tables: ${tableNames}
55
- *
61
+ *
56
62
  * Usage:
57
- *
63
+ *
58
64
  * 1. Configure the client:
59
65
  * \`\`\`ts
60
66
  * import { configure } from './generated';
61
- *
67
+ *
62
68
  * configure({
63
69
  * endpoint: 'https://api.example.com/graphql',
64
70
  * headers: { Authorization: 'Bearer <token>' },
65
71
  * });
66
72
  * \`\`\`
67
- *
73
+ *
68
74
  * 2. Use the hooks:
69
75
  * \`\`\`tsx
70
76
  * import { useCarsQuery, useCreateCarMutation } from './generated';
71
- *
77
+ *
72
78
  * function MyComponent() {
73
79
  * const { data, isLoading } = useCarsQuery({ first: 10 });
74
80
  * const { mutate } = useCreateCarMutation();
@@ -82,7 +88,7 @@ export * from './client';
82
88
 
83
89
  // Entity and filter types
84
90
  export * from './types';
85
-
91
+ ${schemaTypesExport}
86
92
  // Query hooks
87
93
  export * from './queries';
88
94