@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
@@ -1,13 +1,14 @@
1
1
  import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, createTypeAlias, createUnionType, createFilterInterface, } from './ts-ast';
2
2
  import { buildListQueryAST, buildSingleQueryAST, printGraphQL, } from './gql-ast';
3
- import { getTableNames, getListQueryHookName, getSingleQueryHookName, getListQueryFileName, getSingleQueryFileName, getAllRowsQueryName, getSingleRowQueryName, getFilterTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, toScreamingSnake, ucFirst, } from './utils';
3
+ import { getTableNames, getListQueryHookName, getSingleQueryHookName, getListQueryFileName, getSingleQueryFileName, getAllRowsQueryName, getSingleRowQueryName, getFilterTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, getPrimaryKeyInfo, toScreamingSnake, ucFirst, } from './utils';
4
4
  // ============================================================================
5
5
  // List query hook generator
6
6
  // ============================================================================
7
7
  /**
8
8
  * Generate list query hook file content using AST
9
9
  */
10
- export function generateListQueryHook(table) {
10
+ export function generateListQueryHook(table, options = {}) {
11
+ const { reactQueryEnabled = true } = options;
11
12
  const project = createProject();
12
13
  const { typeName, pluralName } = getTableNames(table);
13
14
  const hookName = getListQueryHookName(table);
@@ -20,32 +21,36 @@ export function generateListQueryHook(table) {
20
21
  const queryDocument = printGraphQL(queryAST);
21
22
  const sourceFile = createSourceFile(project, getListQueryFileName(table));
22
23
  // Add file header as leading comment
23
- sourceFile.insertText(0, createFileHeader(`List query hook for ${typeName}`) + '\n\n');
24
+ const headerText = reactQueryEnabled
25
+ ? `List query hook for ${typeName}`
26
+ : `List query functions for ${typeName}`;
27
+ sourceFile.insertText(0, createFileHeader(headerText) + '\n\n');
24
28
  // Collect all filter types used by this table's fields
25
29
  const filterTypesUsed = new Set();
26
30
  for (const field of scalarFields) {
27
- const filterType = getScalarFilterType(field.type.gqlType);
31
+ const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
28
32
  if (filterType) {
29
33
  filterTypesUsed.add(filterType);
30
34
  }
31
35
  }
32
- // Add imports
33
- sourceFile.addImportDeclarations([
34
- createImport({
36
+ // Add imports - conditionally include React Query imports
37
+ const imports = [];
38
+ if (reactQueryEnabled) {
39
+ imports.push(createImport({
35
40
  moduleSpecifier: '@tanstack/react-query',
36
41
  namedImports: ['useQuery'],
37
42
  typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
38
- }),
39
- createImport({
40
- moduleSpecifier: '../client',
41
- namedImports: ['execute'],
42
- typeOnlyNamedImports: ['ExecuteOptions'],
43
- }),
44
- createImport({
45
- moduleSpecifier: '../types',
46
- typeOnlyNamedImports: [typeName, ...Array.from(filterTypesUsed)],
47
- }),
48
- ]);
43
+ }));
44
+ }
45
+ imports.push(createImport({
46
+ moduleSpecifier: '../client',
47
+ namedImports: ['execute'],
48
+ typeOnlyNamedImports: ['ExecuteOptions'],
49
+ }), createImport({
50
+ moduleSpecifier: '../types',
51
+ typeOnlyNamedImports: [typeName, ...Array.from(filterTypesUsed)],
52
+ }));
53
+ sourceFile.addImportDeclarations(imports);
49
54
  // Re-export entity type
50
55
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
51
56
  // Add section comment
@@ -61,12 +66,14 @@ export function generateListQueryHook(table) {
61
66
  // Generate filter interface
62
67
  const fieldFilters = scalarFields
63
68
  .map((field) => {
64
- const filterType = getScalarFilterType(field.type.gqlType);
69
+ const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
65
70
  return filterType ? { fieldName: field.name, filterType } : null;
66
71
  })
67
72
  .filter((f) => f !== null);
68
- sourceFile.addInterface(createFilterInterface(filterTypeName, fieldFilters));
73
+ // Note: Not exported to avoid conflicts with schema-types
74
+ sourceFile.addInterface(createFilterInterface(filterTypeName, fieldFilters, { isExported: false }));
69
75
  // Generate OrderBy type
76
+ // Note: Not exported to avoid conflicts with schema-types
70
77
  const orderByValues = [
71
78
  ...scalarFields.flatMap((f) => [
72
79
  `${toScreamingSnake(f.name)}_ASC`,
@@ -76,7 +83,7 @@ export function generateListQueryHook(table) {
76
83
  'PRIMARY_KEY_ASC',
77
84
  'PRIMARY_KEY_DESC',
78
85
  ];
79
- sourceFile.addTypeAlias(createTypeAlias(orderByTypeName, createUnionType(orderByValues)));
86
+ sourceFile.addTypeAlias(createTypeAlias(orderByTypeName, createUnionType(orderByValues), { isExported: false }));
80
87
  // Variables interface
81
88
  const variablesProps = [
82
89
  { name: 'first', type: 'number', optional: true },
@@ -109,27 +116,28 @@ export function generateListQueryHook(table) {
109
116
  // Query key factory
110
117
  sourceFile.addVariableStatement(createConst(`${queryName}QueryKey`, `(variables?: ${ucFirst(pluralName)}QueryVariables) =>
111
118
  ['${typeName.toLowerCase()}', 'list', variables] as const`));
112
- // Add section comment
113
- sourceFile.addStatements('\n// ============================================================================');
114
- sourceFile.addStatements('// Hook');
115
- sourceFile.addStatements('// ============================================================================\n');
116
- // Hook function
117
- sourceFile.addFunction({
118
- name: hookName,
119
- isExported: true,
120
- parameters: [
121
- {
122
- name: 'variables',
123
- type: `${ucFirst(pluralName)}QueryVariables`,
124
- hasQuestionToken: true,
125
- },
126
- {
127
- name: 'options',
128
- type: `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
129
- hasQuestionToken: true,
130
- },
131
- ],
132
- statements: `return useQuery({
119
+ // Add React Query hook section (only if enabled)
120
+ if (reactQueryEnabled) {
121
+ sourceFile.addStatements('\n// ============================================================================');
122
+ sourceFile.addStatements('// Hook');
123
+ sourceFile.addStatements('// ============================================================================\n');
124
+ // Hook function
125
+ sourceFile.addFunction({
126
+ name: hookName,
127
+ isExported: true,
128
+ parameters: [
129
+ {
130
+ name: 'variables',
131
+ type: `${ucFirst(pluralName)}QueryVariables`,
132
+ hasQuestionToken: true,
133
+ },
134
+ {
135
+ name: 'options',
136
+ type: `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
137
+ hasQuestionToken: true,
138
+ },
139
+ ],
140
+ statements: `return useQuery({
133
141
  queryKey: ${queryName}QueryKey(variables),
134
142
  queryFn: () => execute<${ucFirst(pluralName)}QueryResult, ${ucFirst(pluralName)}QueryVariables>(
135
143
  ${queryName}QueryDocument,
@@ -137,9 +145,9 @@ export function generateListQueryHook(table) {
137
145
  ),
138
146
  ...options,
139
147
  });`,
140
- docs: [
141
- {
142
- description: `Query hook for fetching ${typeName} list
148
+ docs: [
149
+ {
150
+ description: `Query hook for fetching ${typeName} list
143
151
 
144
152
  @example
145
153
  \`\`\`tsx
@@ -149,9 +157,10 @@ const { data, isLoading } = ${hookName}({
149
157
  orderBy: ['CREATED_AT_DESC'],
150
158
  });
151
159
  \`\`\``,
152
- },
153
- ],
154
- });
160
+ },
161
+ ],
162
+ });
163
+ }
155
164
  // Add section comment for standalone functions
156
165
  sourceFile.addStatements('\n// ============================================================================');
157
166
  sourceFile.addStatements('// Standalone Functions (non-React)');
@@ -197,29 +206,30 @@ const data = await queryClient.fetchQuery({
197
206
  },
198
207
  ],
199
208
  });
200
- // Prefetch function (for SSR/QueryClient)
201
- sourceFile.addFunction({
202
- name: `prefetch${ucFirst(pluralName)}Query`,
203
- isExported: true,
204
- isAsync: true,
205
- parameters: [
206
- {
207
- name: 'queryClient',
208
- type: 'QueryClient',
209
- },
210
- {
211
- name: 'variables',
212
- type: `${ucFirst(pluralName)}QueryVariables`,
213
- hasQuestionToken: true,
214
- },
215
- {
216
- name: 'options',
217
- type: 'ExecuteOptions',
218
- hasQuestionToken: true,
219
- },
220
- ],
221
- returnType: 'Promise<void>',
222
- statements: `await queryClient.prefetchQuery({
209
+ // Prefetch function (for SSR/QueryClient) - only if React Query is enabled
210
+ if (reactQueryEnabled) {
211
+ sourceFile.addFunction({
212
+ name: `prefetch${ucFirst(pluralName)}Query`,
213
+ isExported: true,
214
+ isAsync: true,
215
+ parameters: [
216
+ {
217
+ name: 'queryClient',
218
+ type: 'QueryClient',
219
+ },
220
+ {
221
+ name: 'variables',
222
+ type: `${ucFirst(pluralName)}QueryVariables`,
223
+ hasQuestionToken: true,
224
+ },
225
+ {
226
+ name: 'options',
227
+ type: 'ExecuteOptions',
228
+ hasQuestionToken: true,
229
+ },
230
+ ],
231
+ returnType: 'Promise<void>',
232
+ statements: `await queryClient.prefetchQuery({
223
233
  queryKey: ${queryName}QueryKey(variables),
224
234
  queryFn: () => execute<${ucFirst(pluralName)}QueryResult, ${ucFirst(pluralName)}QueryVariables>(
225
235
  ${queryName}QueryDocument,
@@ -227,17 +237,18 @@ const data = await queryClient.fetchQuery({
227
237
  options
228
238
  ),
229
239
  });`,
230
- docs: [
231
- {
232
- description: `Prefetch ${typeName} list for SSR or cache warming
240
+ docs: [
241
+ {
242
+ description: `Prefetch ${typeName} list for SSR or cache warming
233
243
 
234
244
  @example
235
245
  \`\`\`ts
236
246
  await prefetch${ucFirst(pluralName)}Query(queryClient, { first: 10 });
237
247
  \`\`\``,
238
- },
239
- ],
240
- });
248
+ },
249
+ ],
250
+ });
251
+ }
241
252
  return {
242
253
  fileName: getListQueryFileName(table),
243
254
  content: getFormattedOutput(sourceFile),
@@ -249,34 +260,46 @@ await prefetch${ucFirst(pluralName)}Query(queryClient, { first: 10 });
249
260
  /**
250
261
  * Generate single item query hook file content using AST
251
262
  */
252
- export function generateSingleQueryHook(table) {
263
+ export function generateSingleQueryHook(table, options = {}) {
264
+ const { reactQueryEnabled = true } = options;
253
265
  const project = createProject();
254
266
  const { typeName, singularName } = getTableNames(table);
255
267
  const hookName = getSingleQueryHookName(table);
256
268
  const queryName = getSingleRowQueryName(table);
269
+ // Get primary key info dynamically from table constraints
270
+ const pkFields = getPrimaryKeyInfo(table);
271
+ // For simplicity, use first PK field (most common case)
272
+ // Composite PKs would need more complex handling
273
+ const pkField = pkFields[0];
274
+ const pkName = pkField.name;
275
+ const pkTsType = pkField.tsType;
257
276
  // Generate GraphQL document via AST
258
277
  const queryAST = buildSingleQueryAST({ table });
259
278
  const queryDocument = printGraphQL(queryAST);
260
279
  const sourceFile = createSourceFile(project, getSingleQueryFileName(table));
261
280
  // Add file header
262
- sourceFile.insertText(0, createFileHeader(`Single item query hook for ${typeName}`) + '\n\n');
263
- // Add imports
264
- sourceFile.addImportDeclarations([
265
- createImport({
281
+ const headerText = reactQueryEnabled
282
+ ? `Single item query hook for ${typeName}`
283
+ : `Single item query functions for ${typeName}`;
284
+ sourceFile.insertText(0, createFileHeader(headerText) + '\n\n');
285
+ // Add imports - conditionally include React Query imports
286
+ const imports = [];
287
+ if (reactQueryEnabled) {
288
+ imports.push(createImport({
266
289
  moduleSpecifier: '@tanstack/react-query',
267
290
  namedImports: ['useQuery'],
268
291
  typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
269
- }),
270
- createImport({
271
- moduleSpecifier: '../client',
272
- namedImports: ['execute'],
273
- typeOnlyNamedImports: ['ExecuteOptions'],
274
- }),
275
- createImport({
276
- moduleSpecifier: '../types',
277
- typeOnlyNamedImports: [typeName],
278
- }),
279
- ]);
292
+ }));
293
+ }
294
+ imports.push(createImport({
295
+ moduleSpecifier: '../client',
296
+ namedImports: ['execute'],
297
+ typeOnlyNamedImports: ['ExecuteOptions'],
298
+ }), createImport({
299
+ moduleSpecifier: '../types',
300
+ typeOnlyNamedImports: [typeName],
301
+ }));
302
+ sourceFile.addImportDeclarations(imports);
280
303
  // Re-export entity type
281
304
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
282
305
  // Add section comment
@@ -289,9 +312,9 @@ export function generateSingleQueryHook(table) {
289
312
  sourceFile.addStatements('\n// ============================================================================');
290
313
  sourceFile.addStatements('// Types');
291
314
  sourceFile.addStatements('// ============================================================================\n');
292
- // Variables interface
315
+ // Variables interface - use dynamic PK field name and type
293
316
  sourceFile.addInterface(createInterface(`${ucFirst(singularName)}QueryVariables`, [
294
- { name: 'id', type: 'string' },
317
+ { name: pkName, type: pkTsType },
295
318
  ]));
296
319
  // Result interface
297
320
  sourceFile.addInterface(createInterface(`${ucFirst(singularName)}QueryResult`, [
@@ -301,60 +324,62 @@ export function generateSingleQueryHook(table) {
301
324
  sourceFile.addStatements('\n// ============================================================================');
302
325
  sourceFile.addStatements('// Query Key');
303
326
  sourceFile.addStatements('// ============================================================================\n');
304
- // Query key factory
305
- sourceFile.addVariableStatement(createConst(`${queryName}QueryKey`, `(id: string) =>
306
- ['${typeName.toLowerCase()}', 'detail', id] as const`));
307
- // Add section comment
308
- sourceFile.addStatements('\n// ============================================================================');
309
- sourceFile.addStatements('// Hook');
310
- sourceFile.addStatements('// ============================================================================\n');
311
- // Hook function
312
- sourceFile.addFunction({
313
- name: hookName,
314
- isExported: true,
315
- parameters: [
316
- { name: 'id', type: 'string' },
317
- {
318
- name: 'options',
319
- type: `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
320
- hasQuestionToken: true,
321
- },
322
- ],
323
- statements: `return useQuery({
324
- queryKey: ${queryName}QueryKey(id),
327
+ // Query key factory - use dynamic PK field name and type
328
+ sourceFile.addVariableStatement(createConst(`${queryName}QueryKey`, `(${pkName}: ${pkTsType}) =>
329
+ ['${typeName.toLowerCase()}', 'detail', ${pkName}] as const`));
330
+ // Add React Query hook section (only if enabled)
331
+ if (reactQueryEnabled) {
332
+ sourceFile.addStatements('\n// ============================================================================');
333
+ sourceFile.addStatements('// Hook');
334
+ sourceFile.addStatements('// ============================================================================\n');
335
+ // Hook function - use dynamic PK field name and type
336
+ sourceFile.addFunction({
337
+ name: hookName,
338
+ isExported: true,
339
+ parameters: [
340
+ { name: pkName, type: pkTsType },
341
+ {
342
+ name: 'options',
343
+ type: `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
344
+ hasQuestionToken: true,
345
+ },
346
+ ],
347
+ statements: `return useQuery({
348
+ queryKey: ${queryName}QueryKey(${pkName}),
325
349
  queryFn: () => execute<${ucFirst(singularName)}QueryResult, ${ucFirst(singularName)}QueryVariables>(
326
350
  ${queryName}QueryDocument,
327
- { id }
351
+ { ${pkName} }
328
352
  ),
329
- enabled: !!id && (options?.enabled !== false),
353
+ enabled: !!${pkName} && (options?.enabled !== false),
330
354
  ...options,
331
355
  });`,
332
- docs: [
333
- {
334
- description: `Query hook for fetching a single ${typeName} by ID
356
+ docs: [
357
+ {
358
+ description: `Query hook for fetching a single ${typeName} by primary key
335
359
 
336
360
  @example
337
361
  \`\`\`tsx
338
- const { data, isLoading } = ${hookName}('uuid-here');
362
+ const { data, isLoading } = ${hookName}(${pkTsType === 'string' ? "'value-here'" : '123'});
339
363
 
340
364
  if (data?.${queryName}) {
341
- console.log(data.${queryName}.id);
365
+ console.log(data.${queryName}.${pkName});
342
366
  }
343
367
  \`\`\``,
344
- },
345
- ],
346
- });
368
+ },
369
+ ],
370
+ });
371
+ }
347
372
  // Add section comment for standalone functions
348
373
  sourceFile.addStatements('\n// ============================================================================');
349
374
  sourceFile.addStatements('// Standalone Functions (non-React)');
350
375
  sourceFile.addStatements('// ============================================================================\n');
351
- // Fetch function (standalone, no React)
376
+ // Fetch function (standalone, no React) - use dynamic PK
352
377
  sourceFile.addFunction({
353
378
  name: `fetch${ucFirst(singularName)}Query`,
354
379
  isExported: true,
355
380
  isAsync: true,
356
381
  parameters: [
357
- { name: 'id', type: 'string' },
382
+ { name: pkName, type: pkTsType },
358
383
  {
359
384
  name: 'options',
360
385
  type: 'ExecuteOptions',
@@ -364,54 +389,56 @@ if (data?.${queryName}) {
364
389
  returnType: `Promise<${ucFirst(singularName)}QueryResult>`,
365
390
  statements: `return execute<${ucFirst(singularName)}QueryResult, ${ucFirst(singularName)}QueryVariables>(
366
391
  ${queryName}QueryDocument,
367
- { id },
392
+ { ${pkName} },
368
393
  options
369
394
  );`,
370
395
  docs: [
371
396
  {
372
- description: `Fetch a single ${typeName} by ID without React hooks
397
+ description: `Fetch a single ${typeName} by primary key without React hooks
373
398
 
374
399
  @example
375
400
  \`\`\`ts
376
- const data = await fetch${ucFirst(singularName)}Query('uuid-here');
401
+ const data = await fetch${ucFirst(singularName)}Query(${pkTsType === 'string' ? "'value-here'" : '123'});
377
402
  \`\`\``,
378
403
  },
379
404
  ],
380
405
  });
381
- // Prefetch function (for SSR/QueryClient)
382
- sourceFile.addFunction({
383
- name: `prefetch${ucFirst(singularName)}Query`,
384
- isExported: true,
385
- isAsync: true,
386
- parameters: [
387
- { name: 'queryClient', type: 'QueryClient' },
388
- { name: 'id', type: 'string' },
389
- {
390
- name: 'options',
391
- type: 'ExecuteOptions',
392
- hasQuestionToken: true,
393
- },
394
- ],
395
- returnType: 'Promise<void>',
396
- statements: `await queryClient.prefetchQuery({
397
- queryKey: ${queryName}QueryKey(id),
406
+ // Prefetch function (for SSR/QueryClient) - only if React Query is enabled, use dynamic PK
407
+ if (reactQueryEnabled) {
408
+ sourceFile.addFunction({
409
+ name: `prefetch${ucFirst(singularName)}Query`,
410
+ isExported: true,
411
+ isAsync: true,
412
+ parameters: [
413
+ { name: 'queryClient', type: 'QueryClient' },
414
+ { name: pkName, type: pkTsType },
415
+ {
416
+ name: 'options',
417
+ type: 'ExecuteOptions',
418
+ hasQuestionToken: true,
419
+ },
420
+ ],
421
+ returnType: 'Promise<void>',
422
+ statements: `await queryClient.prefetchQuery({
423
+ queryKey: ${queryName}QueryKey(${pkName}),
398
424
  queryFn: () => execute<${ucFirst(singularName)}QueryResult, ${ucFirst(singularName)}QueryVariables>(
399
425
  ${queryName}QueryDocument,
400
- { id },
426
+ { ${pkName} },
401
427
  options
402
428
  ),
403
429
  });`,
404
- docs: [
405
- {
406
- description: `Prefetch a single ${typeName} for SSR or cache warming
430
+ docs: [
431
+ {
432
+ description: `Prefetch a single ${typeName} for SSR or cache warming
407
433
 
408
434
  @example
409
435
  \`\`\`ts
410
- await prefetch${ucFirst(singularName)}Query(queryClient, 'uuid-here');
436
+ await prefetch${ucFirst(singularName)}Query(queryClient, ${pkTsType === 'string' ? "'value-here'" : '123'});
411
437
  \`\`\``,
412
- },
413
- ],
414
- });
438
+ },
439
+ ],
440
+ });
441
+ }
415
442
  return {
416
443
  fileName: getSingleQueryFileName(table),
417
444
  content: getFormattedOutput(sourceFile),
@@ -423,11 +450,11 @@ await prefetch${ucFirst(singularName)}Query(queryClient, 'uuid-here');
423
450
  /**
424
451
  * Generate all query hook files for all tables
425
452
  */
426
- export function generateAllQueryHooks(tables) {
453
+ export function generateAllQueryHooks(tables, options = {}) {
427
454
  const files = [];
428
455
  for (const table of tables) {
429
- files.push(generateListQueryHook(table));
430
- files.push(generateSingleQueryHook(table));
456
+ files.push(generateListQueryHook(table, options));
457
+ files.push(generateSingleQueryHook(table, options));
431
458
  }
432
459
  return files;
433
460
  }
@@ -4,9 +4,11 @@
4
4
  export declare const SCALAR_TS_MAP: Record<string, string>;
5
5
  export declare const SCALAR_FILTER_MAP: Record<string, string>;
6
6
  export declare const SCALAR_NAMES: Set<string>;
7
- export interface ScalarToTsOptions {
7
+ /** All base filter type names - skip these in schema-types.ts to avoid duplicates */
8
+ export declare const BASE_FILTER_TYPE_NAMES: Set<string>;
9
+ export declare function scalarToTsType(scalarName: string, options?: {
8
10
  unknownScalar?: 'unknown' | 'name';
9
11
  overrides?: Record<string, string>;
10
- }
11
- export declare function scalarToTsType(scalarName: string, options?: ScalarToTsOptions): string;
12
- export declare function scalarToFilterType(scalarName: string): string | null;
12
+ }): string;
13
+ /** Get the filter type for a scalar (handles both scalar and array types) */
14
+ export declare function scalarToFilterType(scalarName: string, isArray?: boolean): string | null;
@@ -32,6 +32,8 @@ export const SCALAR_TS_MAP = {
32
32
  MacAddr: 'string',
33
33
  TsVector: 'string',
34
34
  TsQuery: 'string',
35
+ // File upload
36
+ Upload: 'File',
35
37
  };
36
38
  export const SCALAR_FILTER_MAP = {
37
39
  String: 'StringFilter',
@@ -52,15 +54,21 @@ export const SCALAR_FILTER_MAP = {
52
54
  Interval: 'StringFilter',
53
55
  };
54
56
  export const SCALAR_NAMES = new Set(Object.keys(SCALAR_TS_MAP));
57
+ /** Scalars that have list filter variants (e.g., StringListFilter) */
58
+ const LIST_FILTER_SCALARS = new Set(['String', 'Int', 'UUID']);
59
+ /** All base filter type names - skip these in schema-types.ts to avoid duplicates */
60
+ export const BASE_FILTER_TYPE_NAMES = new Set([
61
+ ...new Set(Object.values(SCALAR_FILTER_MAP)),
62
+ ...Array.from(LIST_FILTER_SCALARS).map((s) => `${s}ListFilter`),
63
+ ]);
55
64
  export function scalarToTsType(scalarName, options = {}) {
56
- const override = options.overrides?.[scalarName];
57
- if (override)
58
- return override;
59
- const mapped = SCALAR_TS_MAP[scalarName];
60
- if (mapped)
61
- return mapped;
62
- return options.unknownScalar === 'unknown' ? 'unknown' : scalarName;
65
+ return options.overrides?.[scalarName] ?? SCALAR_TS_MAP[scalarName] ?? (options.unknownScalar === 'unknown' ? 'unknown' : scalarName);
63
66
  }
64
- export function scalarToFilterType(scalarName) {
67
+ /** Get the filter type for a scalar (handles both scalar and array types) */
68
+ export function scalarToFilterType(scalarName, isArray = false) {
69
+ const baseName = scalarName === 'ID' ? 'UUID' : scalarName;
70
+ if (isArray) {
71
+ return LIST_FILTER_SCALARS.has(baseName) ? `${baseName}ListFilter` : null;
72
+ }
65
73
  return SCALAR_FILTER_MAP[scalarName] ?? null;
66
74
  }
@@ -0,0 +1,26 @@
1
+ import type { TypeRegistry } from '../../types/schema';
2
+ export interface GeneratedSchemaTypesFile {
3
+ fileName: string;
4
+ content: string;
5
+ /** List of enum type names that were generated */
6
+ generatedEnums: string[];
7
+ /** List of table entity types that are referenced */
8
+ referencedTableTypes: string[];
9
+ }
10
+ export interface GenerateSchemaTypesOptions {
11
+ /** The TypeRegistry containing all GraphQL types */
12
+ typeRegistry: TypeRegistry;
13
+ /** Type names that already exist in types.ts (table entity types) */
14
+ tableTypeNames: Set<string>;
15
+ }
16
+ export interface PayloadTypesResult {
17
+ generatedTypes: Set<string>;
18
+ referencedTableTypes: Set<string>;
19
+ }
20
+ /**
21
+ * Generate comprehensive schema-types.ts file using ts-morph AST
22
+ *
23
+ * This generates all Input/Payload/Enum types from the TypeRegistry
24
+ * that are needed by custom mutation/query hooks.
25
+ */
26
+ export declare function generateSchemaTypesFile(options: GenerateSchemaTypesOptions): GeneratedSchemaTypesFile;