@constructive-io/graphql-codegen 3.2.1 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/cli/index.js +36 -43
  2. package/cli/shared.d.ts +15 -19
  3. package/cli/shared.js +104 -25
  4. package/client/error.js +31 -9
  5. package/client/execute.js +2 -2
  6. package/client/index.d.ts +3 -3
  7. package/client/index.js +6 -6
  8. package/core/ast.d.ts +1 -1
  9. package/core/ast.js +1 -1
  10. package/core/codegen/babel-ast.d.ts +1 -1
  11. package/core/codegen/babel-ast.js +2 -2
  12. package/core/codegen/barrel.d.ts +0 -6
  13. package/core/codegen/barrel.js +22 -19
  14. package/core/codegen/client.d.ts +2 -12
  15. package/core/codegen/client.js +7 -21
  16. package/core/codegen/custom-mutations.d.ts +0 -14
  17. package/core/codegen/custom-mutations.js +139 -88
  18. package/core/codegen/custom-queries.d.ts +0 -14
  19. package/core/codegen/custom-queries.js +483 -193
  20. package/core/codegen/hooks-ast.d.ts +75 -0
  21. package/core/codegen/hooks-ast.js +522 -0
  22. package/core/codegen/index.d.ts +16 -18
  23. package/core/codegen/index.js +42 -88
  24. package/core/codegen/invalidation.d.ts +1 -7
  25. package/core/codegen/invalidation.js +50 -16
  26. package/core/codegen/mutation-keys.d.ts +1 -10
  27. package/core/codegen/mutation-keys.js +22 -8
  28. package/core/codegen/mutations.d.ts +0 -13
  29. package/core/codegen/mutations.js +301 -366
  30. package/core/codegen/orm/barrel.d.ts +0 -5
  31. package/core/codegen/orm/barrel.js +5 -0
  32. package/core/codegen/orm/client-generator.d.ts +0 -5
  33. package/core/codegen/orm/client-generator.js +7 -2
  34. package/core/codegen/orm/client.js +3 -1
  35. package/core/codegen/orm/custom-ops-generator.d.ts +0 -6
  36. package/core/codegen/orm/custom-ops-generator.js +104 -51
  37. package/core/codegen/orm/index.d.ts +4 -4
  38. package/core/codegen/orm/index.js +28 -15
  39. package/core/codegen/orm/input-types-generator.d.ts +1 -13
  40. package/core/codegen/orm/input-types-generator.js +85 -23
  41. package/core/codegen/orm/model-generator.d.ts +0 -5
  42. package/core/codegen/orm/model-generator.js +309 -131
  43. package/core/codegen/orm/select-types.d.ts +19 -14
  44. package/core/codegen/queries.d.ts +0 -8
  45. package/core/codegen/queries.js +360 -559
  46. package/core/codegen/query-keys.d.ts +1 -1
  47. package/core/codegen/query-keys.js +37 -23
  48. package/core/codegen/scalars.js +3 -1
  49. package/core/codegen/schema-types-generator.d.ts +1 -1
  50. package/core/codegen/schema-types-generator.js +17 -2
  51. package/core/codegen/select-helpers.d.ts +19 -0
  52. package/core/codegen/select-helpers.js +40 -0
  53. package/core/codegen/selection.d.ts +4 -0
  54. package/core/codegen/selection.js +65 -0
  55. package/core/codegen/shared/index.d.ts +2 -15
  56. package/core/codegen/shared/index.js +17 -4
  57. package/core/codegen/templates/hooks-client.ts +49 -0
  58. package/core/codegen/templates/hooks-selection.ts +58 -0
  59. package/core/codegen/templates/orm-client.ts +8 -6
  60. package/core/codegen/templates/query-builder.ts +250 -46
  61. package/core/codegen/templates/select-types.ts +31 -14
  62. package/core/codegen/type-resolver.d.ts +1 -5
  63. package/core/codegen/type-resolver.js +0 -22
  64. package/core/codegen/types.d.ts +0 -3
  65. package/core/codegen/types.js +71 -14
  66. package/core/codegen/utils.d.ts +1 -4
  67. package/core/codegen/utils.js +4 -1
  68. package/core/config/index.d.ts +1 -1
  69. package/core/config/resolver.js +1 -3
  70. package/core/generate.js +38 -50
  71. package/core/index.d.ts +3 -3
  72. package/core/index.js +3 -4
  73. package/core/introspect/index.d.ts +6 -6
  74. package/core/introspect/index.js +5 -8
  75. package/core/introspect/infer-tables.d.ts +0 -14
  76. package/core/introspect/infer-tables.js +15 -1
  77. package/core/introspect/source/database.js +1 -1
  78. package/core/introspect/source/endpoint.d.ts +0 -6
  79. package/core/introspect/source/endpoint.js +7 -1
  80. package/core/introspect/source/index.d.ts +4 -4
  81. package/core/introspect/source/index.js +5 -9
  82. package/core/introspect/source/pgpm-module.js +3 -3
  83. package/core/introspect/transform-schema.d.ts +2 -2
  84. package/core/introspect/transform-schema.js +2 -2
  85. package/core/output/index.d.ts +1 -1
  86. package/core/output/index.js +2 -2
  87. package/core/output/writer.d.ts +3 -0
  88. package/core/output/writer.js +20 -1
  89. package/core/pipeline/index.d.ts +2 -2
  90. package/core/query-builder.d.ts +2 -2
  91. package/core/query-builder.js +1 -1
  92. package/core/watch/index.d.ts +4 -4
  93. package/core/watch/index.js +9 -9
  94. package/core/watch/orchestrator.js +5 -3
  95. package/esm/cli/index.js +37 -44
  96. package/esm/cli/shared.d.ts +15 -19
  97. package/esm/cli/shared.js +94 -23
  98. package/esm/client/error.js +31 -9
  99. package/esm/client/execute.js +2 -2
  100. package/esm/client/index.d.ts +3 -3
  101. package/esm/client/index.js +3 -3
  102. package/esm/core/ast.d.ts +1 -1
  103. package/esm/core/ast.js +1 -1
  104. package/esm/core/codegen/babel-ast.d.ts +1 -1
  105. package/esm/core/codegen/babel-ast.js +2 -2
  106. package/esm/core/codegen/barrel.d.ts +0 -6
  107. package/esm/core/codegen/barrel.js +23 -20
  108. package/esm/core/codegen/client.d.ts +2 -12
  109. package/esm/core/codegen/client.js +7 -21
  110. package/esm/core/codegen/custom-mutations.d.ts +0 -14
  111. package/esm/core/codegen/custom-mutations.js +141 -90
  112. package/esm/core/codegen/custom-queries.d.ts +0 -14
  113. package/esm/core/codegen/custom-queries.js +486 -196
  114. package/esm/core/codegen/hooks-ast.d.ts +75 -0
  115. package/esm/core/codegen/hooks-ast.js +424 -0
  116. package/esm/core/codegen/index.d.ts +16 -18
  117. package/esm/core/codegen/index.js +26 -71
  118. package/esm/core/codegen/invalidation.d.ts +1 -7
  119. package/esm/core/codegen/invalidation.js +51 -17
  120. package/esm/core/codegen/mutation-keys.d.ts +1 -10
  121. package/esm/core/codegen/mutation-keys.js +23 -9
  122. package/esm/core/codegen/mutations.d.ts +0 -13
  123. package/esm/core/codegen/mutations.js +302 -367
  124. package/esm/core/codegen/orm/barrel.d.ts +0 -5
  125. package/esm/core/codegen/orm/barrel.js +6 -1
  126. package/esm/core/codegen/orm/client-generator.d.ts +0 -5
  127. package/esm/core/codegen/orm/client-generator.js +7 -2
  128. package/esm/core/codegen/orm/client.js +3 -1
  129. package/esm/core/codegen/orm/custom-ops-generator.d.ts +0 -6
  130. package/esm/core/codegen/orm/custom-ops-generator.js +103 -50
  131. package/esm/core/codegen/orm/index.d.ts +4 -4
  132. package/esm/core/codegen/orm/index.js +25 -12
  133. package/esm/core/codegen/orm/input-types-generator.d.ts +1 -13
  134. package/esm/core/codegen/orm/input-types-generator.js +85 -23
  135. package/esm/core/codegen/orm/model-generator.d.ts +0 -5
  136. package/esm/core/codegen/orm/model-generator.js +310 -132
  137. package/esm/core/codegen/orm/select-types.d.ts +19 -14
  138. package/esm/core/codegen/queries.d.ts +0 -8
  139. package/esm/core/codegen/queries.js +362 -561
  140. package/esm/core/codegen/query-keys.d.ts +1 -1
  141. package/esm/core/codegen/query-keys.js +38 -24
  142. package/esm/core/codegen/scalars.js +3 -1
  143. package/esm/core/codegen/schema-types-generator.d.ts +1 -1
  144. package/esm/core/codegen/schema-types-generator.js +17 -2
  145. package/esm/core/codegen/select-helpers.d.ts +19 -0
  146. package/esm/core/codegen/select-helpers.js +35 -0
  147. package/esm/core/codegen/selection.d.ts +4 -0
  148. package/esm/core/codegen/selection.js +29 -0
  149. package/esm/core/codegen/shared/index.d.ts +2 -15
  150. package/esm/core/codegen/shared/index.js +16 -3
  151. package/esm/core/codegen/type-resolver.d.ts +1 -5
  152. package/esm/core/codegen/type-resolver.js +1 -22
  153. package/esm/core/codegen/types.d.ts +0 -3
  154. package/esm/core/codegen/types.js +72 -15
  155. package/esm/core/codegen/utils.d.ts +1 -4
  156. package/esm/core/codegen/utils.js +4 -1
  157. package/esm/core/config/index.d.ts +1 -1
  158. package/esm/core/config/resolver.js +2 -4
  159. package/esm/core/generate.js +38 -50
  160. package/esm/core/index.d.ts +3 -3
  161. package/esm/core/index.js +2 -3
  162. package/esm/core/introspect/index.d.ts +6 -6
  163. package/esm/core/introspect/index.js +3 -6
  164. package/esm/core/introspect/infer-tables.d.ts +0 -14
  165. package/esm/core/introspect/infer-tables.js +16 -2
  166. package/esm/core/introspect/source/database.js +2 -2
  167. package/esm/core/introspect/source/endpoint.d.ts +0 -6
  168. package/esm/core/introspect/source/endpoint.js +7 -1
  169. package/esm/core/introspect/source/index.d.ts +4 -4
  170. package/esm/core/introspect/source/index.js +6 -10
  171. package/esm/core/introspect/source/pgpm-module.js +3 -3
  172. package/esm/core/introspect/transform-schema.d.ts +2 -2
  173. package/esm/core/introspect/transform-schema.js +2 -2
  174. package/esm/core/output/index.d.ts +1 -1
  175. package/esm/core/output/index.js +1 -1
  176. package/esm/core/output/writer.d.ts +3 -0
  177. package/esm/core/output/writer.js +20 -1
  178. package/esm/core/pipeline/index.d.ts +2 -2
  179. package/esm/core/pipeline/index.js +2 -2
  180. package/esm/core/query-builder.d.ts +2 -2
  181. package/esm/core/query-builder.js +2 -2
  182. package/esm/core/watch/index.d.ts +4 -4
  183. package/esm/core/watch/index.js +3 -3
  184. package/esm/core/watch/orchestrator.js +5 -3
  185. package/esm/generators/index.d.ts +3 -3
  186. package/esm/generators/index.js +3 -3
  187. package/esm/generators/mutations.d.ts +1 -1
  188. package/esm/generators/select.d.ts +1 -1
  189. package/esm/index.d.ts +3 -3
  190. package/esm/index.js +1 -4
  191. package/esm/types/config.d.ts +0 -10
  192. package/esm/types/config.js +0 -2
  193. package/esm/types/index.d.ts +6 -6
  194. package/esm/types/index.js +1 -1
  195. package/generators/index.d.ts +3 -3
  196. package/generators/index.js +8 -8
  197. package/generators/mutations.d.ts +1 -1
  198. package/generators/select.d.ts +1 -1
  199. package/index.d.ts +3 -3
  200. package/index.js +11 -6
  201. package/package.json +10 -10
  202. package/types/config.d.ts +0 -10
  203. package/types/config.js +0 -2
  204. package/types/index.d.ts +6 -6
  205. package/types/index.js +2 -2
  206. package/core/codegen/gql-ast.d.ts +0 -41
  207. package/core/codegen/gql-ast.js +0 -353
  208. package/core/codegen/schema-gql-ast.d.ts +0 -51
  209. package/core/codegen/schema-gql-ast.js +0 -385
  210. package/core/codegen/templates/client.browser.ts +0 -271
  211. package/core/codegen/templates/client.node.ts +0 -337
  212. package/esm/core/codegen/gql-ast.d.ts +0 -41
  213. package/esm/core/codegen/gql-ast.js +0 -312
  214. package/esm/core/codegen/schema-gql-ast.d.ts +0 -51
  215. package/esm/core/codegen/schema-gql-ast.js +0 -343
@@ -1,322 +1,106 @@
1
+ /**
2
+ * Query hook generators - delegates to ORM model methods (Babel AST-based)
3
+ *
4
+ * Output structure:
5
+ * queries/
6
+ * useCarsQuery.ts - List query hook -> ORM findMany
7
+ * useCarQuery.ts - Single item query hook -> ORM findOne
8
+ */
1
9
  import * as t from '@babel/types';
2
- import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } from './babel-ast';
3
- import { buildListQueryAST, buildSingleQueryAST, printGraphQL, } from './gql-ast';
4
- import { getTableNames, getListQueryHookName, getSingleQueryHookName, getListQueryFileName, getSingleQueryFileName, getAllRowsQueryName, getSingleRowQueryName, getFilterTypeName, getConditionTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, getPrimaryKeyInfo, hasValidPrimaryKey, fieldTypeToTs, toScreamingSnake, ucFirst, lcFirst, getGeneratedFileHeader, } from './utils';
5
- function createUnionType(values) {
6
- return t.tsUnionType(values.map((v) => t.tsLiteralType(t.stringLiteral(v))));
7
- }
8
- function createFilterInterfaceDeclaration(name, fieldFilters, isExported = true) {
9
- const properties = [];
10
- for (const filter of fieldFilters) {
11
- const prop = t.tsPropertySignature(t.identifier(filter.fieldName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filter.filterType))));
12
- prop.optional = true;
13
- properties.push(prop);
14
- }
15
- const andProp = t.tsPropertySignature(t.identifier('and'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(name)))));
16
- andProp.optional = true;
17
- properties.push(andProp);
18
- const orProp = t.tsPropertySignature(t.identifier('or'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(name)))));
19
- orProp.optional = true;
20
- properties.push(orProp);
21
- const notProp = t.tsPropertySignature(t.identifier('not'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(name))));
22
- notProp.optional = true;
23
- properties.push(notProp);
24
- const body = t.tsInterfaceBody(properties);
25
- const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(name), null, null, body);
26
- if (isExported) {
27
- return t.exportNamedDeclaration(interfaceDecl);
28
- }
29
- return interfaceDecl;
30
- }
10
+ import { asConst } from './babel-ast';
11
+ import { addJSDocComment, buildFindManyCallExpr, buildFindOneCallExpr, buildListSelectionArgsCall, buildSelectionArgsCall, callExpr, createFunctionParam, createImportDeclaration, createSAndTDataTypeParams, createSTypeParam, createTypeReExport, destructureParamsWithSelection, destructureParamsWithSelectionAndScope, exportAsyncDeclareFunction, exportAsyncFunction, exportDeclareFunction, exportFunction, generateHookFileCode, listQueryResultType, listSelectionConfigType, objectProp, omitType, returnUseQuery, scopeTypeLiteral, selectionConfigType, singleQueryResultType, spreadObj, sRef, typeRef, useQueryOptionsImplType, voidStatement, withFieldsListSelectionType, withFieldsSelectionType, } from './hooks-ast';
12
+ import { getAllRowsQueryName, getFilterTypeName, getListQueryFileName, getListQueryHookName, getOrderByTypeName, getPrimaryKeyInfo, getSingleQueryFileName, getSingleQueryHookName, getSingleRowQueryName, getTableNames, hasValidPrimaryKey, lcFirst, ucFirst, } from './utils';
31
13
  export function generateListQueryHook(table, options = {}) {
32
14
  const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
33
- const { typeName, pluralName } = getTableNames(table);
15
+ const { typeName, pluralName, singularName } = getTableNames(table);
34
16
  const hookName = getListQueryHookName(table);
35
17
  const queryName = getAllRowsQueryName(table);
36
18
  const filterTypeName = getFilterTypeName(table);
37
- const conditionTypeName = getConditionTypeName(table);
38
19
  const orderByTypeName = getOrderByTypeName(table);
39
- const scalarFields = getScalarFields(table);
40
20
  const keysName = `${lcFirst(typeName)}Keys`;
41
21
  const scopeTypeName = `${typeName}Scope`;
42
- const queryAST = buildListQueryAST({ table });
43
- const queryDocument = printGraphQL(queryAST);
22
+ const selectTypeName = `${typeName}Select`;
23
+ const relationTypeName = `${typeName}WithRelations`;
24
+ const listResultTypeAST = (sel) => listQueryResultType(queryName, relationTypeName, sel);
44
25
  const statements = [];
45
- const filterTypesUsed = new Set();
46
- for (const field of scalarFields) {
47
- const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
48
- if (filterType) {
49
- filterTypesUsed.add(filterType);
50
- }
51
- }
26
+ // Imports
52
27
  if (reactQueryEnabled) {
53
- const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
54
- statements.push(reactQueryImport);
55
- const reactQueryTypeImport = t.importDeclaration([
56
- t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
57
- t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
58
- ], t.stringLiteral('@tanstack/react-query'));
59
- reactQueryTypeImport.importKind = 'type';
60
- statements.push(reactQueryTypeImport);
28
+ statements.push(createImportDeclaration('@tanstack/react-query', ['useQuery']));
29
+ statements.push(createImportDeclaration('@tanstack/react-query', ['UseQueryOptions', 'UseQueryResult', 'QueryClient'], true));
61
30
  }
62
- const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
63
- statements.push(clientImport);
64
- const clientTypeImport = t.importDeclaration([
65
- t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
66
- ], t.stringLiteral('../client'));
67
- clientTypeImport.importKind = 'type';
68
- statements.push(clientTypeImport);
69
- const typesImport = t.importDeclaration([
70
- t.importSpecifier(t.identifier(typeName), t.identifier(typeName)),
71
- ...Array.from(filterTypesUsed).map((ft) => t.importSpecifier(t.identifier(ft), t.identifier(ft))),
72
- ], t.stringLiteral('../types'));
73
- typesImport.importKind = 'type';
74
- statements.push(typesImport);
31
+ statements.push(createImportDeclaration('../client', ['getClient']));
32
+ statements.push(createImportDeclaration('../selection', ['buildListSelectionArgs']));
33
+ statements.push(createImportDeclaration('../selection', ['ListSelectionConfig'], true));
75
34
  if (useCentralizedKeys) {
76
- const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
77
- statements.push(queryKeyImport);
35
+ statements.push(createImportDeclaration('../query-keys', [keysName]));
78
36
  if (hasRelationships) {
79
- const scopeTypeImport = t.importDeclaration([
80
- t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
81
- ], t.stringLiteral('../query-keys'));
82
- scopeTypeImport.importKind = 'type';
83
- statements.push(scopeTypeImport);
37
+ statements.push(createImportDeclaration('../query-keys', [scopeTypeName], true));
84
38
  }
85
39
  }
86
- const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
87
- reExportDecl.exportKind = 'type';
88
- statements.push(reExportDecl);
89
- const queryDocConst = t.variableDeclaration('const', [
90
- t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
91
- t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
92
- ], [])),
93
- ]);
94
- statements.push(t.exportNamedDeclaration(queryDocConst));
95
- const fieldFilters = scalarFields
96
- .map((field) => {
97
- const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
98
- return filterType ? { fieldName: field.name, filterType } : null;
99
- })
100
- .filter((f) => f !== null);
101
- statements.push(createFilterInterfaceDeclaration(filterTypeName, fieldFilters, false));
102
- // Generate Condition interface (simple equality filter with scalar types)
103
- // Track non-primitive types (enums) that need to be imported
104
- const enumTypesUsed = new Set();
105
- const conditionProperties = scalarFields.map((field) => {
106
- const tsType = fieldTypeToTs(field.type);
107
- const isPrimitive = tsType === 'string' ||
108
- tsType === 'number' ||
109
- tsType === 'boolean' ||
110
- tsType === 'unknown' ||
111
- tsType.endsWith('[]');
112
- let typeAnnotation;
113
- if (field.type.isArray) {
114
- const baseType = tsType.replace('[]', '');
115
- const isBasePrimitive = baseType === 'string' ||
116
- baseType === 'number' ||
117
- baseType === 'boolean' ||
118
- baseType === 'unknown';
119
- if (!isBasePrimitive) {
120
- enumTypesUsed.add(baseType);
121
- }
122
- typeAnnotation = t.tsArrayType(baseType === 'string'
123
- ? t.tsStringKeyword()
124
- : baseType === 'number'
125
- ? t.tsNumberKeyword()
126
- : baseType === 'boolean'
127
- ? t.tsBooleanKeyword()
128
- : t.tsTypeReference(t.identifier(baseType)));
129
- }
130
- else {
131
- if (!isPrimitive) {
132
- enumTypesUsed.add(tsType);
133
- }
134
- typeAnnotation =
135
- tsType === 'string'
136
- ? t.tsStringKeyword()
137
- : tsType === 'number'
138
- ? t.tsNumberKeyword()
139
- : tsType === 'boolean'
140
- ? t.tsBooleanKeyword()
141
- : t.tsTypeReference(t.identifier(tsType));
142
- }
143
- const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(typeAnnotation));
144
- prop.optional = true;
145
- return prop;
146
- });
147
- // Add import for enum types if any are used
148
- if (enumTypesUsed.size > 0) {
149
- const schemaTypesImport = t.importDeclaration(Array.from(enumTypesUsed).map((et) => t.importSpecifier(t.identifier(et), t.identifier(et))), t.stringLiteral('../schema-types'));
150
- schemaTypesImport.importKind = 'type';
151
- statements.push(schemaTypesImport);
152
- }
153
- const conditionInterface = t.tsInterfaceDeclaration(t.identifier(conditionTypeName), null, null, t.tsInterfaceBody(conditionProperties));
154
- statements.push(conditionInterface);
155
- const orderByValues = [
156
- ...scalarFields.flatMap((f) => [
157
- `${toScreamingSnake(f.name)}_ASC`,
158
- `${toScreamingSnake(f.name)}_DESC`,
159
- ]),
160
- 'NATURAL',
161
- 'PRIMARY_KEY_ASC',
162
- 'PRIMARY_KEY_DESC',
163
- ];
164
- const orderByTypeAlias = t.tsTypeAliasDeclaration(t.identifier(orderByTypeName), null, createUnionType(orderByValues));
165
- statements.push(orderByTypeAlias);
166
- const variablesInterfaceBody = t.tsInterfaceBody([
167
- (() => {
168
- const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
169
- p.optional = true;
170
- return p;
171
- })(),
172
- (() => {
173
- const p = t.tsPropertySignature(t.identifier('last'), t.tsTypeAnnotation(t.tsNumberKeyword()));
174
- p.optional = true;
175
- return p;
176
- })(),
177
- (() => {
178
- const p = t.tsPropertySignature(t.identifier('offset'), t.tsTypeAnnotation(t.tsNumberKeyword()));
179
- p.optional = true;
180
- return p;
181
- })(),
182
- (() => {
183
- const p = t.tsPropertySignature(t.identifier('before'), t.tsTypeAnnotation(t.tsStringKeyword()));
184
- p.optional = true;
185
- return p;
186
- })(),
187
- (() => {
188
- const p = t.tsPropertySignature(t.identifier('after'), t.tsTypeAnnotation(t.tsStringKeyword()));
189
- p.optional = true;
190
- return p;
191
- })(),
192
- (() => {
193
- const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterTypeName))));
194
- p.optional = true;
195
- return p;
196
- })(),
197
- (() => {
198
- const p = t.tsPropertySignature(t.identifier('condition'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(conditionTypeName))));
199
- p.optional = true;
200
- return p;
201
- })(),
202
- (() => {
203
- const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByTypeName)))));
204
- p.optional = true;
205
- return p;
206
- })(),
207
- ]);
208
- const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(pluralName)}QueryVariables`), null, null, variablesInterfaceBody);
209
- statements.push(t.exportNamedDeclaration(variablesInterface));
210
- const pageInfoType = t.tsTypeLiteral([
211
- t.tsPropertySignature(t.identifier('hasNextPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
212
- t.tsPropertySignature(t.identifier('hasPreviousPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
213
- t.tsPropertySignature(t.identifier('startCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
214
- t.tsPropertySignature(t.identifier('endCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
215
- ]);
216
- const resultType = t.tsTypeLiteral([
217
- t.tsPropertySignature(t.identifier('totalCount'), t.tsTypeAnnotation(t.tsNumberKeyword())),
218
- t.tsPropertySignature(t.identifier('nodes'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(typeName))))),
219
- t.tsPropertySignature(t.identifier('pageInfo'), t.tsTypeAnnotation(pageInfoType)),
220
- ]);
221
- const resultInterfaceBody = t.tsInterfaceBody([
222
- t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(resultType)),
223
- ]);
224
- const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(pluralName)}QueryResult`), null, null, resultInterfaceBody);
225
- statements.push(t.exportNamedDeclaration(resultInterface));
40
+ statements.push(createImportDeclaration('../../orm/input-types', [selectTypeName, relationTypeName, filterTypeName, orderByTypeName], true));
41
+ statements.push(createImportDeclaration('../../orm/select-types', ['FindManyArgs', 'InferSelectResult', 'ConnectionResult', 'StrictSelect'], true));
42
+ // Re-exports
43
+ statements.push(createTypeReExport([selectTypeName, relationTypeName, filterTypeName, orderByTypeName], '../../orm/input-types'));
44
+ // Query key
226
45
  if (useCentralizedKeys) {
227
- const queryKeyConst = t.variableDeclaration('const', [
46
+ const keyDecl = t.exportNamedDeclaration(t.variableDeclaration('const', [
228
47
  t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('list'))),
229
- ]);
230
- const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
231
- addJSDocComment(queryKeyExport, [
48
+ ]));
49
+ addJSDocComment(keyDecl, [
232
50
  'Query key factory - re-exported from query-keys.ts',
233
51
  ]);
234
- statements.push(queryKeyExport);
52
+ statements.push(keyDecl);
235
53
  }
236
54
  else {
237
- const queryKeyArrow = t.arrowFunctionExpression([
238
- typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
239
- ], t.tsAsExpression(t.arrayExpression([
55
+ const keyFn = t.arrowFunctionExpression([
56
+ createFunctionParam('variables', typeRef('FindManyArgs', [
57
+ t.tsUnknownKeyword(),
58
+ typeRef(filterTypeName),
59
+ typeRef(orderByTypeName),
60
+ ]), true),
61
+ ], asConst(t.arrayExpression([
240
62
  t.stringLiteral(typeName.toLowerCase()),
241
63
  t.stringLiteral('list'),
242
64
  t.identifier('variables'),
243
- ]), t.tsTypeReference(t.identifier('const'))));
244
- const queryKeyConst = t.variableDeclaration('const', [
245
- t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
246
- ]);
247
- statements.push(t.exportNamedDeclaration(queryKeyConst));
65
+ ])));
66
+ statements.push(t.exportNamedDeclaration(t.variableDeclaration('const', [
67
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), keyFn),
68
+ ])));
248
69
  }
249
- if (reactQueryEnabled) {
250
- const hookBodyStatements = [];
251
- if (hasRelationships && useCentralizedKeys) {
252
- hookBodyStatements.push(t.variableDeclaration('const', [
253
- t.variableDeclarator(t.objectPattern([
254
- t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
255
- t.restElement(t.identifier('queryOptions')),
256
- ]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
257
- ]));
258
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
259
- t.objectExpression([
260
- t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')])),
261
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
262
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
263
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
264
- ]))),
265
- t.spreadElement(t.identifier('queryOptions')),
266
- ]),
267
- ])));
268
- }
269
- else if (useCentralizedKeys) {
270
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
271
- t.objectExpression([
272
- t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')])),
273
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
274
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
275
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
276
- ]))),
277
- t.spreadElement(t.identifier('options')),
278
- ]),
279
- ])));
280
- }
281
- else {
282
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
283
- t.objectExpression([
284
- t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
285
- t.identifier('variables'),
286
- ])),
287
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
288
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
289
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
290
- ]))),
291
- t.spreadElement(t.identifier('options')),
292
- ]),
293
- ])));
70
+ // Helper for query key call
71
+ const buildListQueryKey = (argsExpr, scopeExpr) => {
72
+ if (useCentralizedKeys) {
73
+ const args = [argsExpr];
74
+ if (scopeExpr)
75
+ args.push(scopeExpr);
76
+ return callExpr(t.memberExpression(t.identifier(keysName), t.identifier('list')), args);
294
77
  }
295
- const hookParams = [
296
- typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
297
- ];
298
- let optionsTypeStr;
78
+ return callExpr(t.identifier(`${queryName}QueryKey`), [argsExpr]);
79
+ };
80
+ // Helper for findMany queryFn
81
+ const buildFindManyFn = () => t.arrowFunctionExpression([], buildFindManyCallExpr(singularName, 'args'));
82
+ // Options type builder with optional scope
83
+ const buildOptionsType = (queryDataType, dataType) => {
84
+ const base = omitType(typeRef('UseQueryOptions', [queryDataType, typeRef('Error'), dataType]), ['queryKey', 'queryFn']);
299
85
  if (hasRelationships && useCentralizedKeys) {
300
- optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
86
+ return t.tsIntersectionType([base, scopeTypeLiteral(scopeTypeName)]);
301
87
  }
302
- else {
303
- optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
304
- }
305
- const optionsParam = t.identifier('options');
306
- optionsParam.optional = true;
307
- optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
308
- hookParams.push(optionsParam);
309
- const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
310
- const hookExport = t.exportNamedDeclaration(hookFunc);
88
+ return base;
89
+ };
90
+ // Hook
91
+ if (reactQueryEnabled) {
311
92
  const docLines = [
312
93
  `Query hook for fetching ${typeName} list`,
313
94
  '',
314
95
  '@example',
315
96
  '```tsx',
316
97
  `const { data, isLoading } = ${hookName}({`,
317
- ' first: 10,',
318
- ' filter: { name: { equalTo: "example" } },',
319
- " orderBy: ['CREATED_AT_DESC'],",
98
+ ' selection: {',
99
+ ' fields: { id: true, name: true },',
100
+ ' where: { name: { equalTo: "example" } },',
101
+ " orderBy: ['CREATED_AT_DESC'],",
102
+ ' first: 10,',
103
+ ' },',
320
104
  '});',
321
105
  '```',
322
106
  ];
@@ -324,364 +108,381 @@ export function generateListQueryHook(table, options = {}) {
324
108
  docLines.push('');
325
109
  docLines.push('@example With scope for hierarchical cache invalidation');
326
110
  docLines.push('```tsx');
327
- docLines.push(`const { data } = ${hookName}(`);
328
- docLines.push(' { first: 10 },');
329
- docLines.push(" { scope: { parentId: 'parent-id' } }");
330
- docLines.push(');');
111
+ docLines.push(`const { data } = ${hookName}({`);
112
+ docLines.push(' selection: { fields: { id: true }, first: 10 },');
113
+ docLines.push(" scope: { parentId: 'parent-id' },");
114
+ docLines.push('});');
331
115
  docLines.push('```');
332
116
  }
333
- addJSDocComment(hookExport, docLines);
334
- statements.push(hookExport);
335
- }
336
- const fetchFuncBody = t.blockStatement([
337
- t.returnStatement(createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
338
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
339
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
340
- ])),
341
- ]);
342
- const fetchFunc = t.functionDeclaration(t.identifier(`fetch${ucFirst(pluralName)}Query`), [
343
- typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
344
- typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
345
- ], fetchFuncBody);
346
- fetchFunc.async = true;
347
- fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
348
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
349
- ])));
350
- const fetchExport = t.exportNamedDeclaration(fetchFunc);
351
- addJSDocComment(fetchExport, [
352
- `Fetch ${typeName} list without React hooks`,
353
- '',
354
- '@example',
355
- '```ts',
356
- '// Direct fetch',
357
- `const data = await fetch${ucFirst(pluralName)}Query({ first: 10 });`,
358
- '',
359
- '// With QueryClient',
360
- 'const data = await queryClient.fetchQuery({',
361
- ` queryKey: ${queryName}QueryKey(variables),`,
362
- ` queryFn: () => fetch${ucFirst(pluralName)}Query(variables),`,
363
- '});',
364
- '```',
365
- ]);
366
- statements.push(fetchExport);
367
- if (reactQueryEnabled) {
368
- const prefetchParams = [
369
- typedParam('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
370
- typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
371
- ];
372
- if (hasRelationships && useCentralizedKeys) {
373
- prefetchParams.push(typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
374
- }
375
- prefetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
376
- let prefetchQueryKeyExpr;
117
+ // Overload 1: with fields
118
+ const o1ParamType = t.tsIntersectionType([
119
+ t.tsTypeLiteral([
120
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsListSelectionType(sRef(), selectTypeName, filterTypeName, orderByTypeName))),
121
+ ]),
122
+ buildOptionsType(listResultTypeAST(sRef()), typeRef('TData')),
123
+ ]);
124
+ const o1 = exportDeclareFunction(hookName, createSAndTDataTypeParams(selectTypeName, listResultTypeAST(sRef())), [createFunctionParam('params', o1ParamType)], typeRef('UseQueryResult', [typeRef('TData')]));
125
+ addJSDocComment(o1, docLines);
126
+ statements.push(o1);
127
+ // Implementation
128
+ const implSelProp = t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(listSelectionConfigType(typeRef(selectTypeName), filterTypeName, orderByTypeName)));
129
+ const implOptionsType = (() => {
130
+ const base = useQueryOptionsImplType();
131
+ if (hasRelationships && useCentralizedKeys) {
132
+ return t.tsIntersectionType([base, scopeTypeLiteral(scopeTypeName)]);
133
+ }
134
+ return base;
135
+ })();
136
+ const implParamType = t.tsIntersectionType([
137
+ t.tsTypeLiteral([implSelProp]),
138
+ implOptionsType,
139
+ ]);
140
+ const body = [];
141
+ body.push(buildListSelectionArgsCall(selectTypeName, filterTypeName, orderByTypeName));
377
142
  if (hasRelationships && useCentralizedKeys) {
378
- prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')]);
379
- }
380
- else if (useCentralizedKeys) {
381
- prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')]);
143
+ body.push(destructureParamsWithSelectionAndScope('queryOptions'));
144
+ body.push(voidStatement('_selection'));
145
+ body.push(returnUseQuery(buildListQueryKey(t.identifier('args'), t.identifier('scope')), buildFindManyFn(), [spreadObj(t.identifier('queryOptions'))]));
382
146
  }
383
147
  else {
384
- prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.identifier('variables')]);
148
+ body.push(destructureParamsWithSelection('queryOptions'));
149
+ body.push(voidStatement('_selection'));
150
+ body.push(returnUseQuery(buildListQueryKey(t.identifier('args')), buildFindManyFn(), [spreadObj(t.identifier('queryOptions'))]));
385
151
  }
386
- const prefetchFuncBody = t.blockStatement([
387
- t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
388
- t.objectExpression([
389
- t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
390
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
391
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
392
- t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
393
- ]))),
394
- ]),
395
- ]))),
152
+ statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
153
+ }
154
+ // Fetch function
155
+ const fetchFnName = `fetch${ucFirst(pluralName)}Query`;
156
+ {
157
+ // Overload 1: with fields
158
+ const f1ParamType = t.tsTypeLiteral([
159
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsListSelectionType(sRef(), selectTypeName, filterTypeName, orderByTypeName))),
160
+ ]);
161
+ const f1Decl = exportAsyncDeclareFunction(fetchFnName, createSTypeParam(selectTypeName), [createFunctionParam('params', f1ParamType)], typeRef('Promise', [listResultTypeAST(sRef())]));
162
+ addJSDocComment(f1Decl, [
163
+ `Fetch ${typeName} list without React hooks`,
164
+ '',
165
+ '@example',
166
+ '```ts',
167
+ `const data = await ${fetchFnName}({`,
168
+ ' selection: {',
169
+ ' fields: { id: true },',
170
+ ' first: 10,',
171
+ ' },',
172
+ '});',
173
+ '```',
174
+ ]);
175
+ statements.push(f1Decl);
176
+ // Implementation
177
+ const fImplParamType = t.tsTypeLiteral([
178
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(listSelectionConfigType(typeRef(selectTypeName), filterTypeName, orderByTypeName))),
396
179
  ]);
397
- const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${ucFirst(pluralName)}Query`), prefetchParams, prefetchFuncBody);
398
- prefetchFunc.async = true;
399
- prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
400
- const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
401
- addJSDocComment(prefetchExport, [
180
+ const fBody = [];
181
+ fBody.push(buildListSelectionArgsCall(selectTypeName, filterTypeName, orderByTypeName));
182
+ fBody.push(t.returnStatement(buildFindManyCallExpr(singularName, 'args')));
183
+ statements.push(exportAsyncFunction(fetchFnName, null, [createFunctionParam('params', fImplParamType)], fBody));
184
+ }
185
+ // Prefetch function
186
+ if (reactQueryEnabled) {
187
+ const prefetchFnName = `prefetch${ucFirst(pluralName)}Query`;
188
+ // Overload 1: with fields
189
+ const p1Params = [
190
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsListSelectionType(sRef(), selectTypeName, filterTypeName, orderByTypeName))),
191
+ ];
192
+ const p1ParamType = hasRelationships && useCentralizedKeys
193
+ ? t.tsIntersectionType([
194
+ t.tsTypeLiteral(p1Params),
195
+ scopeTypeLiteral(scopeTypeName),
196
+ ])
197
+ : t.tsTypeLiteral(p1Params);
198
+ const p1Decl = exportAsyncDeclareFunction(prefetchFnName, createSTypeParam(selectTypeName), [
199
+ createFunctionParam('queryClient', typeRef('QueryClient')),
200
+ createFunctionParam('params', p1ParamType),
201
+ ], typeRef('Promise', [t.tsVoidKeyword()]));
202
+ addJSDocComment(p1Decl, [
402
203
  `Prefetch ${typeName} list for SSR or cache warming`,
403
204
  '',
404
205
  '@example',
405
206
  '```ts',
406
- `await prefetch${ucFirst(pluralName)}Query(queryClient, { first: 10 });`,
207
+ `await ${prefetchFnName}(queryClient, { selection: { fields: { id: true }, first: 10 } });`,
407
208
  '```',
408
209
  ]);
409
- statements.push(prefetchExport);
210
+ statements.push(p1Decl);
211
+ // Implementation
212
+ const pImplParamType = hasRelationships && useCentralizedKeys
213
+ ? t.tsIntersectionType([
214
+ t.tsTypeLiteral([
215
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(listSelectionConfigType(typeRef(selectTypeName), filterTypeName, orderByTypeName))),
216
+ ]),
217
+ scopeTypeLiteral(scopeTypeName),
218
+ ])
219
+ : t.tsTypeLiteral([
220
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(listSelectionConfigType(typeRef(selectTypeName), filterTypeName, orderByTypeName))),
221
+ ]);
222
+ const pBody = [];
223
+ pBody.push(buildListSelectionArgsCall(selectTypeName, filterTypeName, orderByTypeName));
224
+ const queryKeyExpr = hasRelationships && useCentralizedKeys
225
+ ? buildListQueryKey(t.identifier('args'), t.optionalMemberExpression(t.identifier('params'), t.identifier('scope'), false, true))
226
+ : buildListQueryKey(t.identifier('args'));
227
+ const prefetchCall = callExpr(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
228
+ t.objectExpression([
229
+ objectProp('queryKey', queryKeyExpr),
230
+ objectProp('queryFn', buildFindManyFn()),
231
+ ]),
232
+ ]);
233
+ pBody.push(t.expressionStatement(t.awaitExpression(prefetchCall)));
234
+ statements.push(exportAsyncFunction(prefetchFnName, null, [
235
+ createFunctionParam('queryClient', typeRef('QueryClient')),
236
+ createFunctionParam('params', pImplParamType),
237
+ ], pBody, typeRef('Promise', [t.tsVoidKeyword()])));
410
238
  }
411
- const code = generateCode(statements);
412
239
  const headerText = reactQueryEnabled
413
240
  ? `List query hook for ${typeName}`
414
241
  : `List query functions for ${typeName}`;
415
- const content = getGeneratedFileHeader(headerText) + '\n\n' + code;
416
242
  return {
417
243
  fileName: getListQueryFileName(table),
418
- content,
244
+ content: generateHookFileCode(headerText, statements),
419
245
  };
420
246
  }
421
247
  export function generateSingleQueryHook(table, options = {}) {
422
- // Skip tables with composite keys - they are handled as custom queries
423
- if (!hasValidPrimaryKey(table)) {
248
+ if (!hasValidPrimaryKey(table))
424
249
  return null;
425
- }
426
250
  const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
427
251
  const { typeName, singularName } = getTableNames(table);
428
252
  const hookName = getSingleQueryHookName(table);
429
253
  const queryName = getSingleRowQueryName(table);
430
254
  const keysName = `${lcFirst(typeName)}Keys`;
431
255
  const scopeTypeName = `${typeName}Scope`;
256
+ const selectTypeName = `${typeName}Select`;
257
+ const relationTypeName = `${typeName}WithRelations`;
432
258
  const pkFields = getPrimaryKeyInfo(table);
433
259
  const pkField = pkFields[0];
434
- const pkName = pkField.name;
435
- const pkTsType = pkField.tsType;
436
- const queryAST = buildSingleQueryAST({ table });
437
- const queryDocument = printGraphQL(queryAST);
260
+ const pkFieldName = pkField?.name ?? 'id';
261
+ const pkFieldTsType = pkField?.tsType ?? 'string';
262
+ const pkTsType = pkFieldTsType === 'string' ? t.tsStringKeyword() : t.tsNumberKeyword();
263
+ const singleResultTypeAST = (sel) => singleQueryResultType(queryName, relationTypeName, sel);
438
264
  const statements = [];
265
+ // Imports
439
266
  if (reactQueryEnabled) {
440
- const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
441
- statements.push(reactQueryImport);
442
- const reactQueryTypeImport = t.importDeclaration([
443
- t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
444
- t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
445
- ], t.stringLiteral('@tanstack/react-query'));
446
- reactQueryTypeImport.importKind = 'type';
447
- statements.push(reactQueryTypeImport);
267
+ statements.push(createImportDeclaration('@tanstack/react-query', ['useQuery']));
268
+ statements.push(createImportDeclaration('@tanstack/react-query', ['UseQueryOptions', 'UseQueryResult', 'QueryClient'], true));
448
269
  }
449
- const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
450
- statements.push(clientImport);
451
- const clientTypeImport = t.importDeclaration([
452
- t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
453
- ], t.stringLiteral('../client'));
454
- clientTypeImport.importKind = 'type';
455
- statements.push(clientTypeImport);
456
- const typesImport = t.importDeclaration([t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
457
- typesImport.importKind = 'type';
458
- statements.push(typesImport);
270
+ statements.push(createImportDeclaration('../client', ['getClient']));
271
+ statements.push(createImportDeclaration('../selection', ['buildSelectionArgs']));
272
+ statements.push(createImportDeclaration('../selection', ['SelectionConfig'], true));
459
273
  if (useCentralizedKeys) {
460
- const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
461
- statements.push(queryKeyImport);
274
+ statements.push(createImportDeclaration('../query-keys', [keysName]));
462
275
  if (hasRelationships) {
463
- const scopeTypeImport = t.importDeclaration([
464
- t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
465
- ], t.stringLiteral('../query-keys'));
466
- scopeTypeImport.importKind = 'type';
467
- statements.push(scopeTypeImport);
276
+ statements.push(createImportDeclaration('../query-keys', [scopeTypeName], true));
468
277
  }
469
278
  }
470
- const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
471
- reExportDecl.exportKind = 'type';
472
- statements.push(reExportDecl);
473
- const queryDocConst = t.variableDeclaration('const', [
474
- t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
475
- t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
476
- ], [])),
477
- ]);
478
- statements.push(t.exportNamedDeclaration(queryDocConst));
479
- const pkTypeAnnotation = pkTsType === 'string'
480
- ? t.tsStringKeyword()
481
- : pkTsType === 'number'
482
- ? t.tsNumberKeyword()
483
- : t.tsTypeReference(t.identifier(pkTsType));
484
- const variablesInterfaceBody = t.tsInterfaceBody([
485
- t.tsPropertySignature(t.identifier(pkName), t.tsTypeAnnotation(pkTypeAnnotation)),
486
- ]);
487
- const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(singularName)}QueryVariables`), null, null, variablesInterfaceBody);
488
- statements.push(t.exportNamedDeclaration(variablesInterface));
489
- const resultInterfaceBody = t.tsInterfaceBody([
490
- t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(t.tsUnionType([
491
- t.tsTypeReference(t.identifier(typeName)),
492
- t.tsNullKeyword(),
493
- ]))),
494
- ]);
495
- const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(singularName)}QueryResult`), null, null, resultInterfaceBody);
496
- statements.push(t.exportNamedDeclaration(resultInterface));
279
+ statements.push(createImportDeclaration('../../orm/input-types', [selectTypeName, relationTypeName], true));
280
+ statements.push(createImportDeclaration('../../orm/select-types', ['InferSelectResult', 'StrictSelect'], true));
281
+ // Re-exports
282
+ statements.push(createTypeReExport([selectTypeName, relationTypeName], '../../orm/input-types'));
283
+ // Query key
497
284
  if (useCentralizedKeys) {
498
- const queryKeyConst = t.variableDeclaration('const', [
285
+ const keyDecl = t.exportNamedDeclaration(t.variableDeclaration('const', [
499
286
  t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('detail'))),
500
- ]);
501
- const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
502
- addJSDocComment(queryKeyExport, [
287
+ ]));
288
+ addJSDocComment(keyDecl, [
503
289
  'Query key factory - re-exported from query-keys.ts',
504
290
  ]);
505
- statements.push(queryKeyExport);
291
+ statements.push(keyDecl);
506
292
  }
507
293
  else {
508
- const queryKeyArrow = t.arrowFunctionExpression([typedParam(pkName, pkTypeAnnotation)], t.tsAsExpression(t.arrayExpression([
294
+ const keyFn = t.arrowFunctionExpression([createFunctionParam('id', pkTsType)], asConst(t.arrayExpression([
509
295
  t.stringLiteral(typeName.toLowerCase()),
510
296
  t.stringLiteral('detail'),
511
- t.identifier(pkName),
512
- ]), t.tsTypeReference(t.identifier('const'))));
513
- const queryKeyConst = t.variableDeclaration('const', [
514
- t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
515
- ]);
516
- statements.push(t.exportNamedDeclaration(queryKeyConst));
297
+ t.identifier('id'),
298
+ ])));
299
+ statements.push(t.exportNamedDeclaration(t.variableDeclaration('const', [
300
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), keyFn),
301
+ ])));
517
302
  }
518
- if (reactQueryEnabled) {
519
- const hookBodyStatements = [];
520
- if (hasRelationships && useCentralizedKeys) {
521
- hookBodyStatements.push(t.variableDeclaration('const', [
522
- t.variableDeclarator(t.objectPattern([
523
- t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
524
- t.restElement(t.identifier('queryOptions')),
525
- ]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
526
- ]));
527
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
528
- t.objectExpression([
529
- t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
530
- t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
531
- t.identifier('scope'),
532
- ])),
533
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
534
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
535
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
536
- ]))),
537
- t.spreadElement(t.identifier('queryOptions')),
538
- ]),
539
- ])));
303
+ // Helper for query key call
304
+ const buildDetailQueryKey = (pkExpr, scopeExpr) => {
305
+ if (useCentralizedKeys) {
306
+ const args = [pkExpr];
307
+ if (scopeExpr)
308
+ args.push(scopeExpr);
309
+ return callExpr(t.memberExpression(t.identifier(keysName), t.identifier('detail')), args);
540
310
  }
541
- else if (useCentralizedKeys) {
542
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
543
- t.objectExpression([
544
- t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
545
- t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
546
- ])),
547
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
548
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
549
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
550
- ]))),
551
- t.spreadElement(t.identifier('options')),
552
- ]),
553
- ])));
554
- }
555
- else {
556
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
557
- t.objectExpression([
558
- t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
559
- t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
560
- ])),
561
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
562
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
563
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
564
- ]))),
565
- t.spreadElement(t.identifier('options')),
566
- ]),
567
- ])));
568
- }
569
- const hookParams = [
570
- typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
571
- ];
572
- let optionsTypeStr;
311
+ return callExpr(t.identifier(`${queryName}QueryKey`), [pkExpr]);
312
+ };
313
+ // Helper for findOne queryFn
314
+ const buildFindOneFn = () => t.arrowFunctionExpression([], buildFindOneCallExpr(singularName, pkFieldName, 'args'));
315
+ // Options type builder with optional scope
316
+ const buildSingleOptionsType = (queryDataType, dataType) => {
317
+ const base = omitType(typeRef('UseQueryOptions', [queryDataType, typeRef('Error'), dataType]), ['queryKey', 'queryFn']);
573
318
  if (hasRelationships && useCentralizedKeys) {
574
- optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
575
- }
576
- else {
577
- optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
319
+ return t.tsIntersectionType([base, scopeTypeLiteral(scopeTypeName)]);
578
320
  }
579
- const optionsParam = t.identifier('options');
580
- optionsParam.optional = true;
581
- optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
582
- hookParams.push(optionsParam);
583
- const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
584
- const hookExport = t.exportNamedDeclaration(hookFunc);
321
+ return base;
322
+ };
323
+ // Hook
324
+ if (reactQueryEnabled) {
585
325
  const docLines = [
586
326
  `Query hook for fetching a single ${typeName}`,
587
327
  '',
588
328
  '@example',
589
329
  '```tsx',
590
- `const { data, isLoading } = ${hookName}({ ${pkName}: 'some-id' });`,
330
+ `const { data, isLoading } = ${hookName}({`,
331
+ ` ${pkFieldName}: 'some-id',`,
332
+ ' selection: { fields: { id: true, name: true } },',
333
+ '});',
591
334
  '```',
592
335
  ];
593
336
  if (hasRelationships && useCentralizedKeys) {
594
337
  docLines.push('');
595
338
  docLines.push('@example With scope for hierarchical cache invalidation');
596
339
  docLines.push('```tsx');
597
- docLines.push(`const { data } = ${hookName}(`);
598
- docLines.push(` { ${pkName}: 'some-id' },`);
599
- docLines.push(" { scope: { parentId: 'parent-id' } }");
600
- docLines.push(');');
340
+ docLines.push(`const { data } = ${hookName}({`);
341
+ docLines.push(` ${pkFieldName}: 'some-id',`);
342
+ docLines.push(' selection: { fields: { id: true } },');
343
+ docLines.push(" scope: { parentId: 'parent-id' },");
344
+ docLines.push('});');
601
345
  docLines.push('```');
602
346
  }
603
- addJSDocComment(hookExport, docLines);
604
- statements.push(hookExport);
605
- }
606
- const fetchFuncBody = t.blockStatement([
607
- t.returnStatement(createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
608
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
609
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
610
- ])),
611
- ]);
612
- const fetchFunc = t.functionDeclaration(t.identifier(`fetch${ucFirst(singularName)}Query`), [
613
- typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
614
- typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
615
- ], fetchFuncBody);
616
- fetchFunc.async = true;
617
- fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
618
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
619
- ])));
620
- const fetchExport = t.exportNamedDeclaration(fetchFunc);
621
- addJSDocComment(fetchExport, [
622
- `Fetch a single ${typeName} without React hooks`,
623
- '',
624
- '@example',
625
- '```ts',
626
- `const data = await fetch${ucFirst(singularName)}Query({ ${pkName}: 'some-id' });`,
627
- '```',
628
- ]);
629
- statements.push(fetchExport);
630
- if (reactQueryEnabled) {
631
- const prefetchParams = [
632
- typedParam('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
633
- typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
347
+ // Overload 1: with fields
348
+ const o1Props = [
349
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
350
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
634
351
  ];
352
+ const o1ParamType = t.tsIntersectionType([
353
+ t.tsTypeLiteral(o1Props),
354
+ buildSingleOptionsType(singleResultTypeAST(sRef()), typeRef('TData')),
355
+ ]);
356
+ const o1 = exportDeclareFunction(hookName, createSAndTDataTypeParams(selectTypeName, singleResultTypeAST(sRef())), [createFunctionParam('params', o1ParamType)], typeRef('UseQueryResult', [typeRef('TData')]));
357
+ addJSDocComment(o1, docLines);
358
+ statements.push(o1);
359
+ // Implementation
360
+ const implProps = [
361
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
362
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName)))),
363
+ ];
364
+ const implOptionsType = (() => {
365
+ const base = useQueryOptionsImplType();
366
+ if (hasRelationships && useCentralizedKeys) {
367
+ return t.tsIntersectionType([base, scopeTypeLiteral(scopeTypeName)]);
368
+ }
369
+ return base;
370
+ })();
371
+ const implParamType = t.tsIntersectionType([
372
+ t.tsTypeLiteral(implProps),
373
+ implOptionsType,
374
+ ]);
375
+ const body = [];
376
+ body.push(buildSelectionArgsCall(selectTypeName));
377
+ const pkMemberExpr = t.memberExpression(t.identifier('params'), t.identifier(pkFieldName));
635
378
  if (hasRelationships && useCentralizedKeys) {
636
- prefetchParams.push(typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
637
- }
638
- prefetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
639
- let prefetchQueryKeyExpr;
640
- if (hasRelationships && useCentralizedKeys) {
641
- prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
642
- t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
643
- t.identifier('scope'),
644
- ]);
645
- }
646
- else if (useCentralizedKeys) {
647
- prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
379
+ body.push(destructureParamsWithSelectionAndScope('queryOptions'));
380
+ body.push(voidStatement('_selection'));
381
+ body.push(returnUseQuery(buildDetailQueryKey(pkMemberExpr, t.identifier('scope')), buildFindOneFn(), [spreadObj(t.identifier('queryOptions'))]));
648
382
  }
649
383
  else {
650
- prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
384
+ body.push(destructureParamsWithSelection('queryOptions'));
385
+ body.push(voidStatement('_selection'));
386
+ body.push(returnUseQuery(buildDetailQueryKey(pkMemberExpr), buildFindOneFn(), [
387
+ spreadObj(t.identifier('queryOptions')),
388
+ ]));
651
389
  }
652
- const prefetchFuncBody = t.blockStatement([
653
- t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
654
- t.objectExpression([
655
- t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
656
- t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
657
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
658
- t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
659
- ]))),
660
- ]),
661
- ]))),
390
+ statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
391
+ }
392
+ // Fetch function
393
+ const fetchFnName = `fetch${ucFirst(singularName)}Query`;
394
+ {
395
+ // Overload 1: with fields
396
+ const f1Props = [
397
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
398
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
399
+ ];
400
+ const f1Decl = exportAsyncDeclareFunction(fetchFnName, createSTypeParam(selectTypeName), [createFunctionParam('params', t.tsTypeLiteral(f1Props))], typeRef('Promise', [singleResultTypeAST(sRef())]));
401
+ addJSDocComment(f1Decl, [
402
+ `Fetch a single ${typeName} without React hooks`,
403
+ '',
404
+ '@example',
405
+ '```ts',
406
+ `const data = await ${fetchFnName}({`,
407
+ ` ${pkFieldName}: 'some-id',`,
408
+ ' selection: { fields: { id: true } },',
409
+ '});',
410
+ '```',
662
411
  ]);
663
- const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${ucFirst(singularName)}Query`), prefetchParams, prefetchFuncBody);
664
- prefetchFunc.async = true;
665
- prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
666
- const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
667
- addJSDocComment(prefetchExport, [
412
+ statements.push(f1Decl);
413
+ // Implementation
414
+ const fBody = [];
415
+ fBody.push(buildSelectionArgsCall(selectTypeName));
416
+ fBody.push(t.returnStatement(buildFindOneCallExpr(singularName, pkFieldName, 'args')));
417
+ const fImplProps = [
418
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
419
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName)))),
420
+ ];
421
+ statements.push(exportAsyncFunction(fetchFnName, null, [createFunctionParam('params', t.tsTypeLiteral(fImplProps))], fBody));
422
+ }
423
+ // Prefetch function
424
+ if (reactQueryEnabled) {
425
+ const prefetchFnName = `prefetch${ucFirst(singularName)}Query`;
426
+ // Overload 1: with fields
427
+ const p1Props = [
428
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
429
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
430
+ ];
431
+ const p1ParamType = hasRelationships && useCentralizedKeys
432
+ ? t.tsIntersectionType([
433
+ t.tsTypeLiteral(p1Props),
434
+ scopeTypeLiteral(scopeTypeName),
435
+ ])
436
+ : t.tsTypeLiteral(p1Props);
437
+ const p1Decl = exportAsyncDeclareFunction(prefetchFnName, createSTypeParam(selectTypeName), [
438
+ createFunctionParam('queryClient', typeRef('QueryClient')),
439
+ createFunctionParam('params', p1ParamType),
440
+ ], typeRef('Promise', [t.tsVoidKeyword()]));
441
+ addJSDocComment(p1Decl, [
668
442
  `Prefetch a single ${typeName} for SSR or cache warming`,
669
443
  '',
670
444
  '@example',
671
445
  '```ts',
672
- `await prefetch${ucFirst(singularName)}Query(queryClient, { ${pkName}: 'some-id' });`,
446
+ `await ${prefetchFnName}(queryClient, { ${pkFieldName}: 'some-id', selection: { fields: { id: true } } });`,
673
447
  '```',
674
448
  ]);
675
- statements.push(prefetchExport);
449
+ statements.push(p1Decl);
450
+ // Implementation
451
+ const pImplParamType = hasRelationships && useCentralizedKeys
452
+ ? t.tsIntersectionType([
453
+ t.tsTypeLiteral([
454
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
455
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName)))),
456
+ ]),
457
+ scopeTypeLiteral(scopeTypeName),
458
+ ])
459
+ : t.tsTypeLiteral([
460
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
461
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName)))),
462
+ ]);
463
+ const pBody = [];
464
+ pBody.push(buildSelectionArgsCall(selectTypeName));
465
+ const queryKeyExpr = hasRelationships && useCentralizedKeys
466
+ ? buildDetailQueryKey(t.memberExpression(t.identifier('params'), t.identifier(pkFieldName)), t.optionalMemberExpression(t.identifier('params'), t.identifier('scope'), false, true))
467
+ : buildDetailQueryKey(t.memberExpression(t.identifier('params'), t.identifier(pkFieldName)));
468
+ const prefetchCall = callExpr(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
469
+ t.objectExpression([
470
+ objectProp('queryKey', queryKeyExpr),
471
+ objectProp('queryFn', buildFindOneFn()),
472
+ ]),
473
+ ]);
474
+ pBody.push(t.expressionStatement(t.awaitExpression(prefetchCall)));
475
+ statements.push(exportAsyncFunction(prefetchFnName, null, [
476
+ createFunctionParam('queryClient', typeRef('QueryClient')),
477
+ createFunctionParam('params', pImplParamType),
478
+ ], pBody, typeRef('Promise', [t.tsVoidKeyword()])));
676
479
  }
677
- const code = generateCode(statements);
678
480
  const headerText = reactQueryEnabled
679
481
  ? `Single item query hook for ${typeName}`
680
482
  : `Single item query functions for ${typeName}`;
681
- const content = getGeneratedFileHeader(headerText) + '\n\n' + code;
682
483
  return {
683
484
  fileName: getSingleQueryFileName(table),
684
- content,
485
+ content: generateHookFileCode(headerText, statements),
685
486
  };
686
487
  }
687
488
  export function generateAllQueryHooks(tables, options = {}) {