@constructive-io/graphql-codegen 2.24.0 → 2.26.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 (66) hide show
  1. package/README.md +403 -279
  2. package/cli/codegen/babel-ast.d.ts +7 -0
  3. package/cli/codegen/babel-ast.js +15 -0
  4. package/cli/codegen/barrel.js +43 -14
  5. package/cli/codegen/custom-mutations.js +4 -4
  6. package/cli/codegen/custom-queries.js +12 -22
  7. package/cli/codegen/gql-ast.js +22 -1
  8. package/cli/codegen/index.js +1 -0
  9. package/cli/codegen/mutations.d.ts +2 -0
  10. package/cli/codegen/mutations.js +26 -13
  11. package/cli/codegen/orm/client-generator.js +475 -136
  12. package/cli/codegen/orm/custom-ops-generator.js +8 -3
  13. package/cli/codegen/orm/input-types-generator.js +22 -0
  14. package/cli/codegen/orm/model-generator.js +18 -5
  15. package/cli/codegen/orm/select-types.d.ts +33 -0
  16. package/cli/codegen/queries.d.ts +1 -1
  17. package/cli/codegen/queries.js +112 -35
  18. package/cli/codegen/utils.d.ts +6 -0
  19. package/cli/codegen/utils.js +19 -0
  20. package/cli/commands/generate-orm.d.ts +14 -0
  21. package/cli/commands/generate-orm.js +160 -44
  22. package/cli/commands/generate.d.ts +22 -0
  23. package/cli/commands/generate.js +195 -55
  24. package/cli/commands/init.js +29 -9
  25. package/cli/index.js +133 -28
  26. package/cli/watch/orchestrator.d.ts +4 -0
  27. package/cli/watch/orchestrator.js +4 -0
  28. package/esm/cli/codegen/babel-ast.d.ts +7 -0
  29. package/esm/cli/codegen/babel-ast.js +14 -0
  30. package/esm/cli/codegen/barrel.js +44 -15
  31. package/esm/cli/codegen/custom-mutations.js +5 -5
  32. package/esm/cli/codegen/custom-queries.js +13 -23
  33. package/esm/cli/codegen/gql-ast.js +23 -2
  34. package/esm/cli/codegen/index.js +1 -0
  35. package/esm/cli/codegen/mutations.d.ts +2 -0
  36. package/esm/cli/codegen/mutations.js +27 -14
  37. package/esm/cli/codegen/orm/client-generator.js +475 -136
  38. package/esm/cli/codegen/orm/custom-ops-generator.js +8 -3
  39. package/esm/cli/codegen/orm/input-types-generator.js +22 -0
  40. package/esm/cli/codegen/orm/model-generator.js +18 -5
  41. package/esm/cli/codegen/orm/select-types.d.ts +33 -0
  42. package/esm/cli/codegen/queries.d.ts +1 -1
  43. package/esm/cli/codegen/queries.js +114 -37
  44. package/esm/cli/codegen/utils.d.ts +6 -0
  45. package/esm/cli/codegen/utils.js +18 -0
  46. package/esm/cli/commands/generate-orm.d.ts +14 -0
  47. package/esm/cli/commands/generate-orm.js +161 -45
  48. package/esm/cli/commands/generate.d.ts +22 -0
  49. package/esm/cli/commands/generate.js +195 -56
  50. package/esm/cli/commands/init.js +29 -9
  51. package/esm/cli/index.js +134 -29
  52. package/esm/cli/watch/orchestrator.d.ts +4 -0
  53. package/esm/cli/watch/orchestrator.js +5 -1
  54. package/esm/types/config.d.ts +39 -2
  55. package/esm/types/config.js +88 -4
  56. package/esm/types/index.d.ts +2 -2
  57. package/esm/types/index.js +1 -1
  58. package/package.json +10 -7
  59. package/types/config.d.ts +39 -2
  60. package/types/config.js +91 -4
  61. package/types/index.d.ts +2 -2
  62. package/types/index.js +2 -1
  63. package/cli/codegen/orm/query-builder.d.ts +0 -161
  64. package/cli/codegen/orm/query-builder.js +0 -366
  65. package/esm/cli/codegen/orm/query-builder.d.ts +0 -161
  66. package/esm/cli/codegen/orm/query-builder.js +0 -353
@@ -159,9 +159,14 @@ function buildOperationMethod(op, operationType) {
159
159
  const optionsParam = t.identifier('options');
160
160
  optionsParam.optional = true;
161
161
  if (selectTypeName) {
162
+ // Use DeepExact<S, SelectType> to enforce strict field validation
163
+ // This catches invalid fields even when mixed with valid ones
162
164
  optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeLiteral([
163
165
  (() => {
164
- const prop = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier('S'))));
166
+ const prop = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
167
+ t.tsTypeReference(t.identifier('S')),
168
+ t.tsTypeReference(t.identifier(selectTypeName)),
169
+ ]))));
165
170
  prop.optional = true;
166
171
  return prop;
167
172
  })(),
@@ -229,7 +234,7 @@ function generateCustomQueryOpsFile(operations) {
229
234
  // Add imports
230
235
  statements.push(createImportDeclaration('../client', ['OrmClient']));
231
236
  statements.push(createImportDeclaration('../query-builder', ['QueryBuilder', 'buildCustomDocument']));
232
- statements.push(createImportDeclaration('../select-types', ['InferSelectResult'], true));
237
+ statements.push(createImportDeclaration('../select-types', ['InferSelectResult', 'DeepExact'], true));
233
238
  if (allTypeImports.length > 0) {
234
239
  statements.push(createImportDeclaration('../input-types', allTypeImports, true));
235
240
  }
@@ -267,7 +272,7 @@ function generateCustomMutationOpsFile(operations) {
267
272
  // Add imports
268
273
  statements.push(createImportDeclaration('../client', ['OrmClient']));
269
274
  statements.push(createImportDeclaration('../query-builder', ['QueryBuilder', 'buildCustomDocument']));
270
- statements.push(createImportDeclaration('../select-types', ['InferSelectResult'], true));
275
+ statements.push(createImportDeclaration('../select-types', ['InferSelectResult', 'DeepExact'], true));
271
276
  if (allTypeImports.length > 0) {
272
277
  statements.push(createImportDeclaration('../input-types', allTypeImports, true));
273
278
  }
@@ -231,6 +231,22 @@ const SCALAR_FILTER_CONFIGS = [
231
231
  operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'],
232
232
  },
233
233
  { name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
234
+ // List filters (for array fields like string[], int[], uuid[])
235
+ {
236
+ name: 'StringListFilter',
237
+ tsType: 'string[]',
238
+ operators: ['equality', 'distinct', 'comparison', 'listArray'],
239
+ },
240
+ {
241
+ name: 'IntListFilter',
242
+ tsType: 'number[]',
243
+ operators: ['equality', 'distinct', 'comparison', 'listArray'],
244
+ },
245
+ {
246
+ name: 'UUIDListFilter',
247
+ tsType: 'string[]',
248
+ operators: ['equality', 'distinct', 'comparison', 'listArray'],
249
+ },
234
250
  ];
235
251
  /**
236
252
  * Build filter properties based on operator sets
@@ -270,6 +286,12 @@ function buildScalarFilterProperties(config) {
270
286
  if (operators.includes('fulltext')) {
271
287
  props.push({ name: 'matches', type: 'string', optional: true });
272
288
  }
289
+ // List/Array operators (contains, overlaps, anyEqualTo, etc.)
290
+ if (operators.includes('listArray')) {
291
+ // Extract base type from array type (e.g., 'string[]' -> 'string')
292
+ const baseType = tsType.replace('[]', '');
293
+ 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 });
294
+ }
273
295
  return props;
274
296
  }
275
297
  /**
@@ -100,7 +100,7 @@ function generateModelFile(table, _useSharedTypes) {
100
100
  ]));
101
101
  statements.push(createImportDeclaration('../select-types', [
102
102
  'ConnectionResult', 'FindManyArgs', 'FindFirstArgs', 'CreateArgs',
103
- 'UpdateArgs', 'DeleteArgs', 'InferSelectResult',
103
+ 'UpdateArgs', 'DeleteArgs', 'InferSelectResult', 'DeepExact',
104
104
  ], true));
105
105
  statements.push(createImportDeclaration('../input-types', [
106
106
  typeName, relationTypeName, selectTypeName, whereTypeName, orderByTypeName,
@@ -114,10 +114,14 @@ function generateModelFile(table, _useSharedTypes) {
114
114
  paramProp.accessibility = 'private';
115
115
  classBody.push(t.classMethod('constructor', t.identifier('constructor'), [paramProp], t.blockStatement([])));
116
116
  // findMany method
117
+ // Use DeepExact<S, SelectType> to enforce strict field validation
117
118
  const findManyParam = t.identifier('args');
118
119
  findManyParam.optional = true;
119
120
  findManyParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation([
120
- t.tsTypeReference(t.identifier('S')),
121
+ t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
122
+ t.tsTypeReference(t.identifier('S')),
123
+ t.tsTypeReference(t.identifier(selectTypeName)),
124
+ ])),
121
125
  t.tsTypeReference(t.identifier(whereTypeName)),
122
126
  t.tsTypeReference(t.identifier(orderByTypeName)),
123
127
  ])));
@@ -152,7 +156,10 @@ function generateModelFile(table, _useSharedTypes) {
152
156
  const findFirstParam = t.identifier('args');
153
157
  findFirstParam.optional = true;
154
158
  findFirstParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
155
- t.tsTypeReference(t.identifier('S')),
159
+ t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
160
+ t.tsTypeReference(t.identifier('S')),
161
+ t.tsTypeReference(t.identifier(selectTypeName)),
162
+ ])),
156
163
  t.tsTypeReference(t.identifier(whereTypeName)),
157
164
  ])));
158
165
  const findFirstReturnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
@@ -178,7 +185,10 @@ function generateModelFile(table, _useSharedTypes) {
178
185
  // create method
179
186
  const createParam = t.identifier('args');
180
187
  createParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('CreateArgs'), t.tsTypeParameterInstantiation([
181
- t.tsTypeReference(t.identifier('S')),
188
+ t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
189
+ t.tsTypeReference(t.identifier('S')),
190
+ t.tsTypeReference(t.identifier(selectTypeName)),
191
+ ])),
182
192
  t.tsIndexedAccessType(t.tsTypeReference(t.identifier(createInputTypeName)), t.tsLiteralType(t.stringLiteral(singularName))),
183
193
  ])));
184
194
  const createReturnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
@@ -204,7 +214,10 @@ function generateModelFile(table, _useSharedTypes) {
204
214
  if (updateMutationName) {
205
215
  const updateParam = t.identifier('args');
206
216
  updateParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('UpdateArgs'), t.tsTypeParameterInstantiation([
207
- t.tsTypeReference(t.identifier('S')),
217
+ t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
218
+ t.tsTypeReference(t.identifier('S')),
219
+ t.tsTypeReference(t.identifier(selectTypeName)),
220
+ ])),
208
221
  t.tsTypeLiteral([t.tsPropertySignature(t.identifier('id'), t.tsTypeAnnotation(t.tsStringKeyword()))]),
209
222
  t.tsTypeReference(t.identifier(patchTypeName)),
210
223
  ])));
@@ -44,6 +44,39 @@ export interface NestedSelectConfig {
44
44
  filter?: Record<string, unknown>;
45
45
  orderBy?: string[];
46
46
  }
47
+ /**
48
+ * Recursively validates select objects, rejecting unknown keys.
49
+ *
50
+ * This type ensures that users can only select fields that actually exist
51
+ * in the GraphQL schema. It returns `never` if any excess keys are found
52
+ * at any nesting level, causing a TypeScript compile error.
53
+ *
54
+ * Why this is needed:
55
+ * TypeScript's excess property checking has a quirk where it only catches
56
+ * invalid fields when they are the ONLY fields. When mixed with valid fields
57
+ * (e.g., `{ id: true, invalidField: true }`), the structural typing allows
58
+ * the excess property through. This type explicitly checks for and rejects
59
+ * such cases.
60
+ *
61
+ * @example
62
+ * // This will cause a type error because 'invalid' doesn't exist:
63
+ * type Result = DeepExact<{ id: true, invalid: true }, { id?: boolean }>;
64
+ * // Result = never (causes assignment error)
65
+ *
66
+ * @example
67
+ * // This works because all fields are valid:
68
+ * type Result = DeepExact<{ id: true }, { id?: boolean; name?: boolean }>;
69
+ * // Result = { id: true }
70
+ */
71
+ export type DeepExact<T, Shape> = T extends Shape ? Exclude<keyof T, keyof Shape> extends never ? {
72
+ [K in keyof T]: K extends keyof Shape ? T[K] extends {
73
+ select: infer NS;
74
+ } ? Shape[K] extends {
75
+ select?: infer ShapeNS;
76
+ } ? {
77
+ select: DeepExact<NS, NonNullable<ShapeNS>>;
78
+ } : T[K] : T[K] : never;
79
+ } : never : never;
47
80
  /**
48
81
  * Infers the result type from a select configuration
49
82
  *
@@ -17,5 +17,5 @@ export interface QueryGeneratorOptions {
17
17
  hasRelationships?: boolean;
18
18
  }
19
19
  export declare function generateListQueryHook(table: CleanTable, options?: QueryGeneratorOptions): GeneratedQueryFile;
20
- export declare function generateSingleQueryHook(table: CleanTable, options?: QueryGeneratorOptions): GeneratedQueryFile;
20
+ export declare function generateSingleQueryHook(table: CleanTable, options?: QueryGeneratorOptions): GeneratedQueryFile | null;
21
21
  export declare function generateAllQueryHooks(tables: CleanTable[], options?: QueryGeneratorOptions): GeneratedQueryFile[];
@@ -72,6 +72,7 @@ function generateListQueryHook(table, options = {}) {
72
72
  const hookName = (0, utils_1.getListQueryHookName)(table);
73
73
  const queryName = (0, utils_1.getAllRowsQueryName)(table);
74
74
  const filterTypeName = (0, utils_1.getFilterTypeName)(table);
75
+ const conditionTypeName = (0, utils_1.getConditionTypeName)(table);
75
76
  const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
76
77
  const scalarFields = (0, utils_1.getScalarFields)(table);
77
78
  const keysName = `${(0, utils_1.lcFirst)(typeName)}Keys`;
@@ -136,6 +137,59 @@ function generateListQueryHook(table, options = {}) {
136
137
  })
137
138
  .filter((f) => f !== null);
138
139
  statements.push(createFilterInterfaceDeclaration(filterTypeName, fieldFilters, false));
140
+ // Generate Condition interface (simple equality filter with scalar types)
141
+ // Track non-primitive types (enums) that need to be imported
142
+ const enumTypesUsed = new Set();
143
+ const conditionProperties = scalarFields.map((field) => {
144
+ const tsType = (0, utils_1.fieldTypeToTs)(field.type);
145
+ const isPrimitive = tsType === 'string' ||
146
+ tsType === 'number' ||
147
+ tsType === 'boolean' ||
148
+ tsType === 'unknown' ||
149
+ tsType.endsWith('[]');
150
+ let typeAnnotation;
151
+ if (field.type.isArray) {
152
+ const baseType = tsType.replace('[]', '');
153
+ const isBasePrimitive = baseType === 'string' ||
154
+ baseType === 'number' ||
155
+ baseType === 'boolean' ||
156
+ baseType === 'unknown';
157
+ if (!isBasePrimitive) {
158
+ enumTypesUsed.add(baseType);
159
+ }
160
+ typeAnnotation = t.tsArrayType(baseType === 'string'
161
+ ? t.tsStringKeyword()
162
+ : baseType === 'number'
163
+ ? t.tsNumberKeyword()
164
+ : baseType === 'boolean'
165
+ ? t.tsBooleanKeyword()
166
+ : t.tsTypeReference(t.identifier(baseType)));
167
+ }
168
+ else {
169
+ if (!isPrimitive) {
170
+ enumTypesUsed.add(tsType);
171
+ }
172
+ typeAnnotation =
173
+ tsType === 'string'
174
+ ? t.tsStringKeyword()
175
+ : tsType === 'number'
176
+ ? t.tsNumberKeyword()
177
+ : tsType === 'boolean'
178
+ ? t.tsBooleanKeyword()
179
+ : t.tsTypeReference(t.identifier(tsType));
180
+ }
181
+ const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(typeAnnotation));
182
+ prop.optional = true;
183
+ return prop;
184
+ });
185
+ // Add import for enum types if any are used
186
+ if (enumTypesUsed.size > 0) {
187
+ const schemaTypesImport = t.importDeclaration(Array.from(enumTypesUsed).map((et) => t.importSpecifier(t.identifier(et), t.identifier(et))), t.stringLiteral('../schema-types'));
188
+ schemaTypesImport.importKind = 'type';
189
+ statements.push(schemaTypesImport);
190
+ }
191
+ const conditionInterface = t.tsInterfaceDeclaration(t.identifier(conditionTypeName), null, null, t.tsInterfaceBody(conditionProperties));
192
+ statements.push(conditionInterface);
139
193
  const orderByValues = [
140
194
  ...scalarFields.flatMap((f) => [
141
195
  `${(0, utils_1.toScreamingSnake)(f.name)}_ASC`,
@@ -153,16 +207,36 @@ function generateListQueryHook(table, options = {}) {
153
207
  p.optional = true;
154
208
  return p;
155
209
  })(),
210
+ (() => {
211
+ const p = t.tsPropertySignature(t.identifier('last'), t.tsTypeAnnotation(t.tsNumberKeyword()));
212
+ p.optional = true;
213
+ return p;
214
+ })(),
156
215
  (() => {
157
216
  const p = t.tsPropertySignature(t.identifier('offset'), t.tsTypeAnnotation(t.tsNumberKeyword()));
158
217
  p.optional = true;
159
218
  return p;
160
219
  })(),
220
+ (() => {
221
+ const p = t.tsPropertySignature(t.identifier('before'), t.tsTypeAnnotation(t.tsStringKeyword()));
222
+ p.optional = true;
223
+ return p;
224
+ })(),
225
+ (() => {
226
+ const p = t.tsPropertySignature(t.identifier('after'), t.tsTypeAnnotation(t.tsStringKeyword()));
227
+ p.optional = true;
228
+ return p;
229
+ })(),
161
230
  (() => {
162
231
  const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterTypeName))));
163
232
  p.optional = true;
164
233
  return p;
165
234
  })(),
235
+ (() => {
236
+ const p = t.tsPropertySignature(t.identifier('condition'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(conditionTypeName))));
237
+ p.optional = true;
238
+ return p;
239
+ })(),
166
240
  (() => {
167
241
  const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByTypeName)))));
168
242
  p.optional = true;
@@ -222,9 +296,9 @@ function generateListQueryHook(table, options = {}) {
222
296
  hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
223
297
  t.objectExpression([
224
298
  t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')])),
225
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
226
- t.identifier(`${queryName}QueryDocument`),
227
- t.identifier('variables'),
299
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
300
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
301
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
228
302
  ]))),
229
303
  t.spreadElement(t.identifier('queryOptions')),
230
304
  ]),
@@ -234,9 +308,9 @@ function generateListQueryHook(table, options = {}) {
234
308
  hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
235
309
  t.objectExpression([
236
310
  t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')])),
237
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
238
- t.identifier(`${queryName}QueryDocument`),
239
- t.identifier('variables'),
311
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
312
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
313
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
240
314
  ]))),
241
315
  t.spreadElement(t.identifier('options')),
242
316
  ]),
@@ -248,9 +322,9 @@ function generateListQueryHook(table, options = {}) {
248
322
  t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
249
323
  t.identifier('variables'),
250
324
  ])),
251
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
252
- t.identifier(`${queryName}QueryDocument`),
253
- t.identifier('variables'),
325
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
326
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
327
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
254
328
  ]))),
255
329
  t.spreadElement(t.identifier('options')),
256
330
  ]),
@@ -298,10 +372,9 @@ function generateListQueryHook(table, options = {}) {
298
372
  statements.push(hookExport);
299
373
  }
300
374
  const fetchFuncBody = t.blockStatement([
301
- t.returnStatement(t.callExpression(t.identifier('execute'), [
302
- t.identifier(`${queryName}QueryDocument`),
303
- t.identifier('variables'),
304
- t.identifier('options'),
375
+ t.returnStatement((0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
376
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
377
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
305
378
  ])),
306
379
  ]);
307
380
  const fetchFunc = t.functionDeclaration(t.identifier(`fetch${(0, utils_1.ucFirst)(pluralName)}Query`), [
@@ -352,10 +425,9 @@ function generateListQueryHook(table, options = {}) {
352
425
  t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
353
426
  t.objectExpression([
354
427
  t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
355
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
356
- t.identifier(`${queryName}QueryDocument`),
357
- t.identifier('variables'),
358
- t.identifier('options'),
428
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
429
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
430
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
359
431
  ]))),
360
432
  ]),
361
433
  ]))),
@@ -385,6 +457,10 @@ function generateListQueryHook(table, options = {}) {
385
457
  };
386
458
  }
387
459
  function generateSingleQueryHook(table, options = {}) {
460
+ // Skip tables with composite keys - they are handled as custom queries
461
+ if (!(0, utils_1.hasValidPrimaryKey)(table)) {
462
+ return null;
463
+ }
388
464
  const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
389
465
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
390
466
  const hookName = (0, utils_1.getSingleQueryHookName)(table);
@@ -492,9 +568,9 @@ function generateSingleQueryHook(table, options = {}) {
492
568
  t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
493
569
  t.identifier('scope'),
494
570
  ])),
495
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
496
- t.identifier(`${queryName}QueryDocument`),
497
- t.identifier('variables'),
571
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
572
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
573
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
498
574
  ]))),
499
575
  t.spreadElement(t.identifier('queryOptions')),
500
576
  ]),
@@ -506,9 +582,9 @@ function generateSingleQueryHook(table, options = {}) {
506
582
  t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
507
583
  t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
508
584
  ])),
509
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
510
- t.identifier(`${queryName}QueryDocument`),
511
- t.identifier('variables'),
585
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
586
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
587
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
512
588
  ]))),
513
589
  t.spreadElement(t.identifier('options')),
514
590
  ]),
@@ -520,9 +596,9 @@ function generateSingleQueryHook(table, options = {}) {
520
596
  t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
521
597
  t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
522
598
  ])),
523
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
524
- t.identifier(`${queryName}QueryDocument`),
525
- t.identifier('variables'),
599
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
600
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
601
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
526
602
  ]))),
527
603
  t.spreadElement(t.identifier('options')),
528
604
  ]),
@@ -566,10 +642,9 @@ function generateSingleQueryHook(table, options = {}) {
566
642
  statements.push(hookExport);
567
643
  }
568
644
  const fetchFuncBody = t.blockStatement([
569
- t.returnStatement(t.callExpression(t.identifier('execute'), [
570
- t.identifier(`${queryName}QueryDocument`),
571
- t.identifier('variables'),
572
- t.identifier('options'),
645
+ t.returnStatement((0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
646
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
647
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
573
648
  ])),
574
649
  ]);
575
650
  const fetchFunc = t.functionDeclaration(t.identifier(`fetch${(0, utils_1.ucFirst)(singularName)}Query`), [
@@ -616,10 +691,9 @@ function generateSingleQueryHook(table, options = {}) {
616
691
  t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
617
692
  t.objectExpression([
618
693
  t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
619
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
620
- t.identifier(`${queryName}QueryDocument`),
621
- t.identifier('variables'),
622
- t.identifier('options'),
694
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
695
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
696
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
623
697
  ]))),
624
698
  ]),
625
699
  ]))),
@@ -652,7 +726,10 @@ function generateAllQueryHooks(tables, options = {}) {
652
726
  const files = [];
653
727
  for (const table of tables) {
654
728
  files.push(generateListQueryHook(table, options));
655
- files.push(generateSingleQueryHook(table, options));
729
+ const singleHook = generateSingleQueryHook(table, options);
730
+ if (singleHook) {
731
+ files.push(singleHook);
732
+ }
656
733
  }
657
734
  return files;
658
735
  }
@@ -171,6 +171,12 @@ export declare function getPrimaryKeyInfo(table: CleanTable): PrimaryKeyField[];
171
171
  * Get primary key field names (convenience wrapper)
172
172
  */
173
173
  export declare function getPrimaryKeyFields(table: CleanTable): string[];
174
+ /**
175
+ * Check if table has a valid single-field primary key
176
+ * Used to determine if a single query hook can be generated
177
+ * Tables with composite keys return false (handled as custom queries)
178
+ */
179
+ export declare function hasValidPrimaryKey(table: CleanTable): boolean;
174
180
  /**
175
181
  * Generate query key prefix for a table
176
182
  * e.g., "cars" for list queries, "car" for detail queries
@@ -35,6 +35,7 @@ exports.isRelationField = isRelationField;
35
35
  exports.getScalarFields = getScalarFields;
36
36
  exports.getPrimaryKeyInfo = getPrimaryKeyInfo;
37
37
  exports.getPrimaryKeyFields = getPrimaryKeyFields;
38
+ exports.hasValidPrimaryKey = hasValidPrimaryKey;
38
39
  exports.getQueryKeyPrefix = getQueryKeyPrefix;
39
40
  exports.getGeneratedFileHeader = getGeneratedFileHeader;
40
41
  exports.indent = indent;
@@ -331,6 +332,24 @@ function getPrimaryKeyInfo(table) {
331
332
  function getPrimaryKeyFields(table) {
332
333
  return getPrimaryKeyInfo(table).map((pk) => pk.name);
333
334
  }
335
+ /**
336
+ * Check if table has a valid single-field primary key
337
+ * Used to determine if a single query hook can be generated
338
+ * Tables with composite keys return false (handled as custom queries)
339
+ */
340
+ function hasValidPrimaryKey(table) {
341
+ // Check for explicit primary key constraint with single field
342
+ const pk = table.constraints?.primaryKey?.[0];
343
+ if (pk && pk.fields.length === 1) {
344
+ return true;
345
+ }
346
+ // Check for 'id' field as fallback
347
+ const idField = table.fields.find((f) => f.name.toLowerCase() === 'id');
348
+ if (idField) {
349
+ return true;
350
+ }
351
+ return false;
352
+ }
334
353
  // ============================================================================
335
354
  // Query key generation
336
355
  // ============================================================================
@@ -9,6 +9,8 @@
9
9
  export interface GenerateOrmOptions {
10
10
  /** Path to config file */
11
11
  config?: string;
12
+ /** Named target in a multi-target config */
13
+ target?: string;
12
14
  /** GraphQL endpoint URL (overrides config) */
13
15
  endpoint?: string;
14
16
  /** Path to GraphQL schema file (.graphql) */
@@ -24,9 +26,21 @@ export interface GenerateOrmOptions {
24
26
  /** Skip custom operations (only generate table CRUD) */
25
27
  skipCustomOperations?: boolean;
26
28
  }
29
+ export interface GenerateOrmTargetResult {
30
+ name: string;
31
+ output: string;
32
+ success: boolean;
33
+ message: string;
34
+ tables?: string[];
35
+ customQueries?: string[];
36
+ customMutations?: string[];
37
+ filesWritten?: string[];
38
+ errors?: string[];
39
+ }
27
40
  export interface GenerateOrmResult {
28
41
  success: boolean;
29
42
  message: string;
43
+ targets?: GenerateOrmTargetResult[];
30
44
  tables?: string[];
31
45
  customQueries?: string[];
32
46
  customMutations?: string[];