@constructive-io/graphql-codegen 2.21.0 → 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 (93) hide show
  1. package/cli/codegen/barrel.d.ts +4 -1
  2. package/cli/codegen/barrel.js +18 -12
  3. package/cli/codegen/client.js +33 -0
  4. package/cli/codegen/custom-mutations.d.ts +4 -0
  5. package/cli/codegen/custom-mutations.js +39 -13
  6. package/cli/codegen/custom-queries.d.ts +4 -0
  7. package/cli/codegen/custom-queries.js +36 -11
  8. package/cli/codegen/gql-ast.js +9 -5
  9. package/cli/codegen/index.js +35 -7
  10. package/cli/codegen/mutations.d.ts +2 -0
  11. package/cli/codegen/mutations.js +87 -23
  12. package/cli/codegen/orm/barrel.js +4 -2
  13. package/cli/codegen/orm/index.js +17 -0
  14. package/cli/codegen/orm/input-types-generator.js +83 -29
  15. package/cli/codegen/orm/model-generator.js +6 -4
  16. package/cli/codegen/queries.js +36 -27
  17. package/cli/codegen/scalars.d.ts +6 -4
  18. package/cli/codegen/scalars.js +17 -9
  19. package/cli/codegen/schema-types-generator.d.ts +26 -0
  20. package/cli/codegen/schema-types-generator.js +365 -0
  21. package/cli/codegen/ts-ast.d.ts +3 -1
  22. package/cli/codegen/ts-ast.js +2 -2
  23. package/cli/codegen/type-resolver.d.ts +52 -6
  24. package/cli/codegen/type-resolver.js +97 -19
  25. package/cli/codegen/types.d.ts +7 -4
  26. package/cli/codegen/types.js +94 -41
  27. package/cli/codegen/utils.d.ts +20 -2
  28. package/cli/codegen/utils.js +32 -7
  29. package/cli/commands/generate-orm.js +5 -5
  30. package/cli/commands/generate.d.ts +4 -1
  31. package/cli/commands/generate.js +27 -8
  32. package/cli/introspect/transform-schema.d.ts +33 -21
  33. package/cli/introspect/transform-schema.js +31 -21
  34. package/esm/cli/codegen/barrel.d.ts +4 -1
  35. package/esm/cli/codegen/barrel.js +18 -12
  36. package/esm/cli/codegen/client.js +33 -0
  37. package/esm/cli/codegen/custom-mutations.d.ts +4 -0
  38. package/esm/cli/codegen/custom-mutations.js +40 -14
  39. package/esm/cli/codegen/custom-queries.d.ts +4 -0
  40. package/esm/cli/codegen/custom-queries.js +37 -12
  41. package/esm/cli/codegen/gql-ast.js +10 -6
  42. package/esm/cli/codegen/index.js +35 -7
  43. package/esm/cli/codegen/mutations.d.ts +2 -0
  44. package/esm/cli/codegen/mutations.js +88 -24
  45. package/esm/cli/codegen/orm/barrel.js +4 -2
  46. package/esm/cli/codegen/orm/index.js +17 -0
  47. package/esm/cli/codegen/orm/input-types-generator.js +83 -29
  48. package/esm/cli/codegen/orm/model-generator.js +7 -5
  49. package/esm/cli/codegen/queries.js +37 -28
  50. package/esm/cli/codegen/scalars.d.ts +6 -4
  51. package/esm/cli/codegen/scalars.js +16 -8
  52. package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
  53. package/esm/cli/codegen/schema-types-generator.js +362 -0
  54. package/esm/cli/codegen/ts-ast.d.ts +3 -1
  55. package/esm/cli/codegen/ts-ast.js +2 -2
  56. package/esm/cli/codegen/type-resolver.d.ts +52 -6
  57. package/esm/cli/codegen/type-resolver.js +97 -20
  58. package/esm/cli/codegen/types.d.ts +7 -4
  59. package/esm/cli/codegen/types.js +95 -41
  60. package/esm/cli/codegen/utils.d.ts +20 -2
  61. package/esm/cli/codegen/utils.js +31 -7
  62. package/esm/cli/commands/generate-orm.js +5 -5
  63. package/esm/cli/commands/generate.d.ts +4 -1
  64. package/esm/cli/commands/generate.js +27 -8
  65. package/esm/cli/introspect/transform-schema.d.ts +33 -21
  66. package/esm/cli/introspect/transform-schema.js +31 -21
  67. package/esm/types/schema.d.ts +2 -0
  68. package/package.json +8 -6
  69. package/types/schema.d.ts +2 -0
  70. package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  71. package/__tests__/codegen/input-types-generator.test.js +0 -635
  72. package/__tests__/codegen/react-query-optional.test.d.ts +0 -1
  73. package/__tests__/codegen/react-query-optional.test.js +0 -292
  74. package/cli/codegen/filters.d.ts +0 -27
  75. package/cli/codegen/filters.js +0 -357
  76. package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  77. package/cli/codegen/orm/input-types-generator.test.js +0 -75
  78. package/cli/codegen/orm/select-types.test.d.ts +0 -11
  79. package/cli/codegen/orm/select-types.test.js +0 -22
  80. package/cli/introspect/transform-schema.test.d.ts +0 -1
  81. package/cli/introspect/transform-schema.test.js +0 -67
  82. package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  83. package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
  84. package/esm/__tests__/codegen/react-query-optional.test.d.ts +0 -1
  85. package/esm/__tests__/codegen/react-query-optional.test.js +0 -290
  86. package/esm/cli/codegen/filters.d.ts +0 -27
  87. package/esm/cli/codegen/filters.js +0 -351
  88. package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  89. package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
  90. package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
  91. package/esm/cli/codegen/orm/select-types.test.js +0 -21
  92. package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
  93. package/esm/cli/introspect/transform-schema.test.js +0 -65
@@ -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
 
@@ -53,6 +53,39 @@ export function getConfig(): GraphQLClientConfig {
53
53
  return globalConfig;
54
54
  }
55
55
 
56
+ /**
57
+ * Set a single header value
58
+ * Useful for updating Authorization after login
59
+ *
60
+ * @example
61
+ * \`\`\`ts
62
+ * setHeader('Authorization', 'Bearer <new-token>');
63
+ * \`\`\`
64
+ */
65
+ export function setHeader(key: string, value: string): void {
66
+ const config = getConfig();
67
+ globalConfig = {
68
+ ...config,
69
+ headers: { ...config.headers, [key]: value },
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Merge multiple headers into the current configuration
75
+ *
76
+ * @example
77
+ * \`\`\`ts
78
+ * setHeaders({ Authorization: 'Bearer <token>', 'X-Custom': 'value' });
79
+ * \`\`\`
80
+ */
81
+ export function setHeaders(headers: Record<string, string>): void {
82
+ const config = getConfig();
83
+ globalConfig = {
84
+ ...config,
85
+ headers: { ...config.headers, ...headers },
86
+ };
87
+ }
88
+
56
89
  // ============================================================================
57
90
  // Error handling
58
91
  // ============================================================================
@@ -23,6 +23,8 @@ export interface GenerateCustomMutationHookOptions {
23
23
  skipQueryField?: boolean;
24
24
  /** Whether to generate React Query hooks (default: true for backwards compatibility) */
25
25
  reactQueryEnabled?: boolean;
26
+ /** Table entity type names (for import path resolution) */
27
+ tableTypeNames?: Set<string>;
26
28
  }
27
29
  /**
28
30
  * Generate a custom mutation hook file
@@ -36,6 +38,8 @@ export interface GenerateAllCustomMutationHooksOptions {
36
38
  skipQueryField?: boolean;
37
39
  /** Whether to generate React Query hooks (default: true for backwards compatibility) */
38
40
  reactQueryEnabled?: boolean;
41
+ /** Table entity type names (for import path resolution) */
42
+ tableTypeNames?: Set<string>;
39
43
  }
40
44
  /**
41
45
  * Generate all custom mutation hook files
@@ -1,6 +1,6 @@
1
1
  import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
2
2
  import { buildCustomMutationString } from './schema-gql-ast';
3
- import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, } from './type-resolver';
3
+ import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, createTypeTracker, } from './type-resolver';
4
4
  /**
5
5
  * Generate a custom mutation hook file
6
6
  * When reactQueryEnabled is false, returns null since mutations require React Query
@@ -21,13 +21,15 @@ export function generateCustomMutationHook(options) {
21
21
  }
22
22
  }
23
23
  function generateCustomMutationHookInternal(options) {
24
- const { operation, typeRegistry, maxDepth = 2, skipQueryField = true } = options;
24
+ const { operation, typeRegistry, maxDepth = 2, skipQueryField = true, tableTypeNames } = options;
25
25
  const project = createProject();
26
26
  const hookName = getOperationHookName(operation.name, 'mutation');
27
27
  const fileName = getOperationFileName(operation.name, 'mutation');
28
28
  const variablesTypeName = getOperationVariablesTypeName(operation.name, 'mutation');
29
29
  const resultTypeName = getOperationResultTypeName(operation.name, 'mutation');
30
30
  const documentConstName = getDocumentConstName(operation.name, 'mutation');
31
+ // Create type tracker to collect referenced types (with table type awareness)
32
+ const tracker = createTypeTracker({ tableTypeNames });
31
33
  // Generate GraphQL document
32
34
  const mutationDocument = buildCustomMutationString({
33
35
  operation,
@@ -38,8 +40,21 @@ function generateCustomMutationHookInternal(options) {
38
40
  const sourceFile = createSourceFile(project, fileName);
39
41
  // Add file header
40
42
  sourceFile.insertText(0, createFileHeader(`Custom mutation hook for ${operation.name}`) + '\n\n');
43
+ // Generate variables interface if there are arguments (with tracking)
44
+ let variablesProps = [];
45
+ if (operation.args.length > 0) {
46
+ variablesProps = generateVariablesProperties(operation.args, tracker);
47
+ }
48
+ // Generate result interface (with tracking)
49
+ const resultType = typeRefToTsType(operation.returnType, tracker);
50
+ const resultProps = [
51
+ { name: operation.name, type: resultType },
52
+ ];
53
+ // Get importable types from tracker (separated by source)
54
+ const schemaTypes = tracker.getImportableTypes(); // From schema-types.ts
55
+ const tableTypes = tracker.getTableTypes(); // From types.ts
41
56
  // Add imports
42
- sourceFile.addImportDeclarations([
57
+ const imports = [
43
58
  createImport({
44
59
  moduleSpecifier: '@tanstack/react-query',
45
60
  namedImports: ['useMutation'],
@@ -49,21 +64,31 @@ function generateCustomMutationHookInternal(options) {
49
64
  moduleSpecifier: '../client',
50
65
  namedImports: ['execute'],
51
66
  }),
52
- ]);
67
+ ];
68
+ // Add types.ts import for table entity types
69
+ if (tableTypes.length > 0) {
70
+ imports.push(createImport({
71
+ moduleSpecifier: '../types',
72
+ typeOnlyNamedImports: tableTypes,
73
+ }));
74
+ }
75
+ // Add schema-types import for Input/Payload/Enum types
76
+ if (schemaTypes.length > 0) {
77
+ imports.push(createImport({
78
+ moduleSpecifier: '../schema-types',
79
+ typeOnlyNamedImports: schemaTypes,
80
+ }));
81
+ }
82
+ sourceFile.addImportDeclarations(imports);
53
83
  // Add mutation document constant
54
84
  sourceFile.addVariableStatement(createConst(documentConstName, '`\n' + mutationDocument + '`', {
55
85
  docs: ['GraphQL mutation document'],
56
86
  }));
57
- // Generate variables interface if there are arguments
87
+ // Add variables interface
58
88
  if (operation.args.length > 0) {
59
- const variablesProps = generateVariablesProperties(operation.args);
60
89
  sourceFile.addInterface(createInterface(variablesTypeName, variablesProps));
61
90
  }
62
- // Generate result interface
63
- const resultType = typeRefToTsType(operation.returnType);
64
- const resultProps = [
65
- { name: operation.name, type: resultType },
66
- ];
91
+ // Add result interface
67
92
  sourceFile.addInterface(createInterface(resultTypeName, resultProps));
68
93
  // Generate hook function
69
94
  const hookParams = generateHookParameters(operation, variablesTypeName, resultTypeName);
@@ -87,10 +112,10 @@ function generateCustomMutationHookInternal(options) {
87
112
  /**
88
113
  * Generate interface properties from CleanArguments
89
114
  */
90
- function generateVariablesProperties(args) {
115
+ function generateVariablesProperties(args, tracker) {
91
116
  return args.map((arg) => ({
92
117
  name: arg.name,
93
- type: typeRefToTsType(arg.type),
118
+ type: typeRefToTsType(arg.type, tracker),
94
119
  optional: !isTypeRequired(arg.type),
95
120
  docs: arg.description ? [arg.description] : undefined,
96
121
  }));
@@ -139,7 +164,7 @@ function generateHookBody(operation, documentConstName, variablesTypeName, resul
139
164
  * When reactQueryEnabled is false, returns empty array since mutations require React Query
140
165
  */
141
166
  export function generateAllCustomMutationHooks(options) {
142
- const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true } = options;
167
+ const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
143
168
  return operations
144
169
  .filter((op) => op.kind === 'mutation')
145
170
  .map((operation) => generateCustomMutationHook({
@@ -148,6 +173,7 @@ export function generateAllCustomMutationHooks(options) {
148
173
  maxDepth,
149
174
  skipQueryField,
150
175
  reactQueryEnabled,
176
+ tableTypeNames,
151
177
  }))
152
178
  .filter((result) => result !== null);
153
179
  }
@@ -23,6 +23,8 @@ export interface GenerateCustomQueryHookOptions {
23
23
  skipQueryField?: boolean;
24
24
  /** Whether to generate React Query hooks (default: true for backwards compatibility) */
25
25
  reactQueryEnabled?: boolean;
26
+ /** Table entity type names (for import path resolution) */
27
+ tableTypeNames?: Set<string>;
26
28
  }
27
29
  /**
28
30
  * Generate a custom query hook file
@@ -35,6 +37,8 @@ export interface GenerateAllCustomQueryHooksOptions {
35
37
  skipQueryField?: boolean;
36
38
  /** Whether to generate React Query hooks (default: true for backwards compatibility) */
37
39
  reactQueryEnabled?: boolean;
40
+ /** Table entity type names (for import path resolution) */
41
+ tableTypeNames?: Set<string>;
38
42
  }
39
43
  /**
40
44
  * Generate all custom query hook files
@@ -1,12 +1,12 @@
1
1
  import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
2
2
  import { buildCustomQueryString } from './schema-gql-ast';
3
- import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, getQueryKeyName, } from './type-resolver';
3
+ import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, getQueryKeyName, createTypeTracker, } from './type-resolver';
4
4
  import { ucFirst } from './utils';
5
5
  /**
6
6
  * Generate a custom query hook file
7
7
  */
8
8
  export function generateCustomQueryHook(options) {
9
- const { operation, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true } = options;
9
+ const { operation, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
10
10
  const project = createProject();
11
11
  const hookName = getOperationHookName(operation.name, 'query');
12
12
  const fileName = getOperationFileName(operation.name, 'query');
@@ -14,6 +14,8 @@ export function generateCustomQueryHook(options) {
14
14
  const resultTypeName = getOperationResultTypeName(operation.name, 'query');
15
15
  const documentConstName = getDocumentConstName(operation.name, 'query');
16
16
  const queryKeyName = getQueryKeyName(operation.name);
17
+ // Create type tracker to collect referenced types (with table type awareness)
18
+ const tracker = createTypeTracker({ tableTypeNames });
17
19
  // Generate GraphQL document
18
20
  const queryDocument = buildCustomQueryString({
19
21
  operation,
@@ -27,6 +29,19 @@ export function generateCustomQueryHook(options) {
27
29
  ? `Custom query hook for ${operation.name}`
28
30
  : `Custom query functions for ${operation.name}`;
29
31
  sourceFile.insertText(0, createFileHeader(headerText) + '\n\n');
32
+ // Generate variables interface if there are arguments (with tracking)
33
+ let variablesProps = [];
34
+ if (operation.args.length > 0) {
35
+ variablesProps = generateVariablesProperties(operation.args, tracker);
36
+ }
37
+ // Generate result interface (with tracking)
38
+ const resultType = typeRefToTsType(operation.returnType, tracker);
39
+ const resultProps = [
40
+ { name: operation.name, type: resultType },
41
+ ];
42
+ // Get importable types from tracker (separated by source)
43
+ const schemaTypes = tracker.getImportableTypes(); // From schema-types.ts
44
+ const tableTypes = tracker.getTableTypes(); // From types.ts
30
45
  // Add imports - conditionally include React Query imports
31
46
  const imports = [];
32
47
  if (reactQueryEnabled) {
@@ -41,21 +56,30 @@ export function generateCustomQueryHook(options) {
41
56
  namedImports: ['execute'],
42
57
  typeOnlyNamedImports: ['ExecuteOptions'],
43
58
  }));
59
+ // Add types.ts import for table entity types
60
+ if (tableTypes.length > 0) {
61
+ imports.push(createImport({
62
+ moduleSpecifier: '../types',
63
+ typeOnlyNamedImports: tableTypes,
64
+ }));
65
+ }
66
+ // Add schema-types import for Input/Payload/Enum types
67
+ if (schemaTypes.length > 0) {
68
+ imports.push(createImport({
69
+ moduleSpecifier: '../schema-types',
70
+ typeOnlyNamedImports: schemaTypes,
71
+ }));
72
+ }
44
73
  sourceFile.addImportDeclarations(imports);
45
74
  // Add query document constant
46
75
  sourceFile.addVariableStatement(createConst(documentConstName, '`\n' + queryDocument + '`', {
47
76
  docs: ['GraphQL query document'],
48
77
  }));
49
- // Generate variables interface if there are arguments
78
+ // Add variables interface
50
79
  if (operation.args.length > 0) {
51
- const variablesProps = generateVariablesProperties(operation.args);
52
80
  sourceFile.addInterface(createInterface(variablesTypeName, variablesProps));
53
81
  }
54
- // Generate result interface
55
- const resultType = typeRefToTsType(operation.returnType);
56
- const resultProps = [
57
- { name: operation.name, type: resultType },
58
- ];
82
+ // Add result interface
59
83
  sourceFile.addInterface(createInterface(resultTypeName, resultProps));
60
84
  // Query key factory
61
85
  if (operation.args.length > 0) {
@@ -126,10 +150,10 @@ export function generateCustomQueryHook(options) {
126
150
  /**
127
151
  * Generate interface properties from CleanArguments
128
152
  */
129
- function generateVariablesProperties(args) {
153
+ function generateVariablesProperties(args, tracker) {
130
154
  return args.map((arg) => ({
131
155
  name: arg.name,
132
- type: typeRefToTsType(arg.type),
156
+ type: typeRefToTsType(arg.type, tracker),
133
157
  optional: !isTypeRequired(arg.type),
134
158
  docs: arg.description ? [arg.description] : undefined,
135
159
  }));
@@ -351,7 +375,7 @@ await ${fnName}(queryClient);
351
375
  * Generate all custom query hook files
352
376
  */
353
377
  export function generateAllCustomQueryHooks(options) {
354
- const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true } = options;
378
+ const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
355
379
  return operations
356
380
  .filter((op) => op.kind === 'query')
357
381
  .map((operation) => generateCustomQueryHook({
@@ -360,5 +384,6 @@ export function generateAllCustomQueryHooks(options) {
360
384
  maxDepth,
361
385
  skipQueryField,
362
386
  reactQueryEnabled,
387
+ tableTypeNames,
363
388
  }));
364
389
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import * as t from 'gql-ast';
8
8
  import { print } from 'graphql';
9
- import { getTableNames, getAllRowsQueryName, getSingleRowQueryName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getFilterTypeName, getOrderByTypeName, getScalarFields, ucFirst, } from './utils';
9
+ import { getTableNames, getAllRowsQueryName, getSingleRowQueryName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getFilterTypeName, getOrderByTypeName, getScalarFields, getPrimaryKeyInfo, ucFirst, } from './utils';
10
10
  // ============================================================================
11
11
  // Field selection builders
12
12
  // ============================================================================
@@ -106,16 +106,20 @@ export function buildSingleQueryAST(config) {
106
106
  const { table } = config;
107
107
  const queryName = getSingleRowQueryName(table);
108
108
  const scalarFields = getScalarFields(table);
109
- // Variable definitions - assume UUID primary key
109
+ // Get primary key info dynamically from table constraints
110
+ const pkFields = getPrimaryKeyInfo(table);
111
+ // For simplicity, use first PK field (most common case)
112
+ const pkField = pkFields[0];
113
+ // Variable definitions - use dynamic PK field name and type
110
114
  const variableDefinitions = [
111
115
  t.variableDefinition({
112
- variable: t.variable({ name: 'id' }),
113
- type: t.nonNullType({ type: t.namedType({ type: 'UUID' }) }),
116
+ variable: t.variable({ name: pkField.name }),
117
+ type: t.nonNullType({ type: t.namedType({ type: pkField.gqlType }) }),
114
118
  }),
115
119
  ];
116
- // Query arguments
120
+ // Query arguments - use dynamic PK field name
117
121
  const args = [
118
- t.argument({ name: 'id', value: t.variable({ name: 'id' }) }),
122
+ t.argument({ name: pkField.name, value: t.variable({ name: pkField.name }) }),
119
123
  ];
120
124
  // Field selections
121
125
  const fieldSelections = createFieldSelections(scalarFields);