@constructive-io/graphql-codegen 3.2.1 → 3.3.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 (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,441 +1,376 @@
1
+ /**
2
+ * Mutation hook generators - delegates to ORM model methods (Babel AST-based)
3
+ *
4
+ * Output structure:
5
+ * mutations/
6
+ * useCreateCarMutation.ts -> ORM create
7
+ * useUpdateCarMutation.ts -> ORM update
8
+ * useDeleteCarMutation.ts -> ORM delete
9
+ */
1
10
  import * as t from '@babel/types';
2
- import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } from './babel-ast';
3
- import { buildCreateMutationAST, buildUpdateMutationAST, buildDeleteMutationAST, printGraphQL, } from './gql-ast';
4
- import { getTableNames, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, getCreateMutationFileName, getUpdateMutationFileName, getDeleteMutationFileName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getScalarFields, getPrimaryKeyInfo, fieldTypeToTs, ucFirst, lcFirst, getGeneratedFileHeader, } from './utils';
5
- function isAutoGeneratedField(fieldName, pkFieldNames) {
6
- const name = fieldName.toLowerCase();
7
- if (pkFieldNames.has(fieldName))
8
- return true;
9
- const timestampPatterns = [
10
- 'createdat', 'created_at', 'createddate', 'created_date',
11
- 'updatedat', 'updated_at', 'updateddate', 'updated_date',
12
- 'deletedat', 'deleted_at',
13
- ];
14
- return timestampPatterns.includes(name);
11
+ import { addJSDocComment, buildSelectionArgsCall, callExpr, constDecl, createFunctionParam, createImportDeclaration, createSTypeParam, createTypeReExport, destructureParamsWithSelection, exportDeclareFunction, exportFunction, generateHookFileCode, getClientCallUnwrap, inferSelectResultType, objectProp, omitType, returnUseMutation, selectionConfigType, shorthandProp, spreadObj, sRef, typeRef, typeLiteralWithProps, useMutationOptionsType, useMutationResultType, voidStatement, } from './hooks-ast';
12
+ import { getCreateMutationFileName, getCreateMutationHookName, getCreateMutationName, getDeleteMutationFileName, getDeleteMutationHookName, getDeleteMutationName, getPrimaryKeyInfo, getTableNames, getUpdateMutationFileName, getUpdateMutationHookName, getUpdateMutationName, hasValidPrimaryKey, lcFirst, } from './utils';
13
+ function buildMutationResultType(mutationName, singularName, relationTypeName, selectType) {
14
+ return typeLiteralWithProps([
15
+ {
16
+ name: mutationName,
17
+ type: typeLiteralWithProps([
18
+ {
19
+ name: singularName,
20
+ type: inferSelectResultType(relationTypeName, selectType),
21
+ },
22
+ ]),
23
+ },
24
+ ]);
25
+ }
26
+ function buildFieldsSelectionType(s, selectTypeName) {
27
+ return t.tsParenthesizedType(t.tsIntersectionType([
28
+ t.tsTypeLiteral([
29
+ t.tsPropertySignature(t.identifier('fields'), t.tsTypeAnnotation(s)),
30
+ ]),
31
+ typeRef('StrictSelect', [s, typeRef(selectTypeName)]),
32
+ ]));
15
33
  }
16
34
  export function generateCreateMutationHook(table, options = {}) {
17
- const { reactQueryEnabled = true, enumsFromSchemaTypes = [], useCentralizedKeys = true, hasRelationships = false, tableTypeNames = new Set(), } = options;
18
- if (!reactQueryEnabled) {
35
+ const { reactQueryEnabled = true, useCentralizedKeys = true } = options;
36
+ if (!reactQueryEnabled)
19
37
  return null;
20
- }
21
- const enumSet = new Set(enumsFromSchemaTypes);
22
38
  const { typeName, singularName } = getTableNames(table);
23
39
  const hookName = getCreateMutationHookName(table);
40
+ const mutationName = getCreateMutationName(table);
24
41
  const keysName = `${lcFirst(typeName)}Keys`;
25
42
  const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
26
- const scopeTypeName = `${typeName}Scope`;
27
- const mutationName = getCreateMutationName(table);
28
- const scalarFields = getScalarFields(table);
29
- const pkFieldNames = new Set(getPrimaryKeyInfo(table).map((pk) => pk.name));
30
- const usedEnums = new Set();
31
- const usedTableTypes = new Set();
32
- for (const field of scalarFields) {
33
- const cleanType = field.type.gqlType.replace(/!/g, '');
34
- if (enumSet.has(cleanType)) {
35
- usedEnums.add(cleanType);
36
- }
37
- else if (tableTypeNames.has(cleanType) && cleanType !== typeName) {
38
- // Track table types used in scalar fields (excluding the main type which is already imported)
39
- usedTableTypes.add(cleanType);
40
- }
41
- }
42
- const mutationAST = buildCreateMutationAST({ table });
43
- const mutationDocument = printGraphQL(mutationAST);
43
+ const selectTypeName = `${typeName}Select`;
44
+ const relationTypeName = `${typeName}WithRelations`;
45
+ const createInputTypeName = `Create${typeName}Input`;
44
46
  const statements = [];
45
- const reactQueryImport = t.importDeclaration([
46
- t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
47
- t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
48
- ], t.stringLiteral('@tanstack/react-query'));
49
- statements.push(reactQueryImport);
50
- const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
51
- reactQueryTypeImport.importKind = 'type';
52
- statements.push(reactQueryTypeImport);
53
- const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
54
- statements.push(clientImport);
55
- // Import the main type and any other table types used in scalar fields
56
- const allTypesToImport = [typeName, ...Array.from(usedTableTypes)].sort();
57
- const typesImport = t.importDeclaration(allTypesToImport.map((t_) => t.importSpecifier(t.identifier(t_), t.identifier(t_))), t.stringLiteral('../types'));
58
- typesImport.importKind = 'type';
59
- statements.push(typesImport);
60
- if (usedEnums.size > 0) {
61
- const enumImport = t.importDeclaration(Array.from(usedEnums).sort().map((e) => t.importSpecifier(t.identifier(e), t.identifier(e))), t.stringLiteral('../schema-types'));
62
- enumImport.importKind = 'type';
63
- statements.push(enumImport);
64
- }
65
- if (useCentralizedKeys) {
66
- const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
67
- statements.push(queryKeyImport);
68
- if (hasRelationships) {
69
- const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
70
- scopeTypeImport.importKind = 'type';
71
- statements.push(scopeTypeImport);
72
- }
73
- const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
74
- statements.push(mutationKeyImport);
75
- }
76
- const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
77
- reExportDecl.exportKind = 'type';
78
- statements.push(reExportDecl);
79
- const mutationDocConst = t.variableDeclaration('const', [
80
- t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
81
- ]);
82
- statements.push(t.exportNamedDeclaration(mutationDocConst));
83
- const inputFields = scalarFields
84
- .filter((f) => !isAutoGeneratedField(f.name, pkFieldNames))
85
- .map((f) => {
86
- const prop = t.tsPropertySignature(t.identifier(f.name), t.tsTypeAnnotation(t.tsUnionType([
87
- t.tsTypeReference(t.identifier(fieldTypeToTs(f.type))),
88
- t.tsNullKeyword(),
89
- ])));
90
- prop.optional = true;
91
- return prop;
92
- });
93
- const createInputInterface = t.tsInterfaceDeclaration(t.identifier(`${typeName}CreateInput`), null, null, t.tsInterfaceBody(inputFields));
94
- addJSDocComment(createInputInterface, [`Input type for creating a ${typeName}`]);
95
- statements.push(createInputInterface);
96
- const variablesInterfaceBody = t.tsInterfaceBody([
97
- t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
98
- t.tsPropertySignature(t.identifier(lcFirst(typeName)), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${typeName}CreateInput`)))),
99
- ]))),
100
- ]);
101
- const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
102
- statements.push(t.exportNamedDeclaration(variablesInterface));
103
- const resultInterfaceBody = t.tsInterfaceBody([
104
- t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([
105
- t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(typeName)))),
106
- ]))),
107
- ]);
108
- const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationResult`), null, null, resultInterfaceBody);
109
- statements.push(t.exportNamedDeclaration(resultInterface));
110
- const hookBodyStatements = [];
111
- hookBodyStatements.push(t.variableDeclaration('const', [
112
- t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
47
+ // Imports
48
+ statements.push(createImportDeclaration('@tanstack/react-query', [
49
+ 'useMutation',
50
+ 'useQueryClient',
113
51
  ]));
114
- const mutationOptions = [];
52
+ statements.push(createImportDeclaration('@tanstack/react-query', ['UseMutationOptions', 'UseMutationResult'], true));
53
+ statements.push(createImportDeclaration('../client', ['getClient']));
54
+ statements.push(createImportDeclaration('../selection', ['buildSelectionArgs']));
55
+ statements.push(createImportDeclaration('../selection', ['SelectionConfig'], true));
115
56
  if (useCentralizedKeys) {
116
- mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.callExpression(t.memberExpression(t.identifier(mutationKeysName), t.identifier('create')), [])));
57
+ statements.push(createImportDeclaration('../query-keys', [keysName]));
58
+ statements.push(createImportDeclaration('../mutation-keys', [mutationKeysName]));
117
59
  }
118
- mutationOptions.push(t.objectProperty(t.identifier('mutationFn'), t.arrowFunctionExpression([typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], [
119
- t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationResult`)),
120
- t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)),
121
- ]))));
122
- const invalidateQueryKey = useCentralizedKeys
123
- ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
124
- : t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
125
- mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([], t.blockStatement([
126
- t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), invalidateQueryKey)])])),
127
- ]))));
128
- mutationOptions.push(t.spreadElement(t.identifier('options')));
129
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
130
- const optionsTypeStr = `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`;
131
- const optionsParam = t.identifier('options');
132
- optionsParam.optional = true;
133
- optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
134
- const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
135
- const hookExport = t.exportNamedDeclaration(hookFunc);
136
- addJSDocComment(hookExport, [
60
+ statements.push(createImportDeclaration('../../orm/input-types', [selectTypeName, relationTypeName, createInputTypeName], true));
61
+ statements.push(createImportDeclaration('../../orm/select-types', ['InferSelectResult', 'StrictSelect'], true));
62
+ // Re-exports
63
+ statements.push(createTypeReExport([selectTypeName, relationTypeName, createInputTypeName], '../../orm/input-types'));
64
+ // Variable type: CreateTypeName['singularName']
65
+ const createVarType = t.tsIndexedAccessType(typeRef(createInputTypeName), t.tsLiteralType(t.stringLiteral(singularName)));
66
+ const resultType = (sel) => buildMutationResultType(mutationName, singularName, relationTypeName, sel);
67
+ // Overload 1: with fields
68
+ const o1ParamType = t.tsIntersectionType([
69
+ t.tsTypeLiteral([
70
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(buildFieldsSelectionType(sRef(), selectTypeName))),
71
+ ]),
72
+ useMutationOptionsType(resultType(sRef()), createVarType),
73
+ ]);
74
+ const o1 = exportDeclareFunction(hookName, createSTypeParam(selectTypeName), [createFunctionParam('params', o1ParamType)], useMutationResultType(resultType(sRef()), createVarType));
75
+ addJSDocComment(o1, [
137
76
  `Mutation hook for creating a ${typeName}`,
138
77
  '',
139
78
  '@example',
140
79
  '```tsx',
141
- `const { mutate, isPending } = ${hookName}();`,
142
- '',
143
- 'mutate({',
144
- ' input: {',
145
- ` ${lcFirst(typeName)}: {`,
146
- ' // ... fields',
147
- ' },',
148
- ' },',
80
+ `const { mutate, isPending } = ${hookName}({`,
81
+ ' selection: { fields: { id: true, name: true } },',
149
82
  '});',
83
+ '',
84
+ "mutate({ name: 'New item' });",
150
85
  '```',
151
86
  ]);
152
- statements.push(hookExport);
153
- const code = generateCode(statements);
154
- const content = getGeneratedFileHeader(`Create mutation hook for ${typeName}`) + '\n\n' + code;
87
+ statements.push(o1);
88
+ // Implementation
89
+ const implSelProp = t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName))));
90
+ const implParamType = t.tsIntersectionType([
91
+ t.tsTypeLiteral([implSelProp]),
92
+ omitType(typeRef('UseMutationOptions', [
93
+ t.tsAnyKeyword(),
94
+ typeRef('Error'),
95
+ createVarType,
96
+ ]), ['mutationFn']),
97
+ ]);
98
+ const body = [];
99
+ body.push(buildSelectionArgsCall(selectTypeName));
100
+ body.push(destructureParamsWithSelection('mutationOptions'));
101
+ body.push(voidStatement('_selection'));
102
+ body.push(constDecl('queryClient', callExpr('useQueryClient', [])));
103
+ const mutationKeyExpr = useCentralizedKeys
104
+ ? callExpr(t.memberExpression(t.identifier(mutationKeysName), t.identifier('create')), [])
105
+ : undefined;
106
+ // mutationFn: (data: CreateInput['singular']) => getClient().singular.create({ data, select: ... }).unwrap()
107
+ const dataParam = createFunctionParam('data', createVarType);
108
+ const mutationFnExpr = t.arrowFunctionExpression([dataParam], getClientCallUnwrap(singularName, 'create', t.objectExpression([
109
+ shorthandProp('data'),
110
+ objectProp('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
111
+ ])));
112
+ // onSuccess: invalidate lists
113
+ const listKeyExpr = useCentralizedKeys
114
+ ? callExpr(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
115
+ : t.arrayExpression([
116
+ t.stringLiteral(typeName.toLowerCase()),
117
+ t.stringLiteral('list'),
118
+ ]);
119
+ const onSuccessFn = t.arrowFunctionExpression([], t.blockStatement([
120
+ t.expressionStatement(callExpr(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([objectProp('queryKey', listKeyExpr)])])),
121
+ ]));
122
+ body.push(returnUseMutation(mutationFnExpr, [
123
+ objectProp('onSuccess', onSuccessFn),
124
+ spreadObj(t.identifier('mutationOptions')),
125
+ ], mutationKeyExpr));
126
+ statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
155
127
  return {
156
128
  fileName: getCreateMutationFileName(table),
157
- content,
129
+ content: generateHookFileCode(`Create mutation hook for ${typeName}`, statements),
158
130
  };
159
131
  }
160
132
  export function generateUpdateMutationHook(table, options = {}) {
161
- const { reactQueryEnabled = true, enumsFromSchemaTypes = [], useCentralizedKeys = true, hasRelationships = false, tableTypeNames = new Set(), } = options;
162
- if (!reactQueryEnabled) {
133
+ const { reactQueryEnabled = true, useCentralizedKeys = true } = options;
134
+ if (!reactQueryEnabled)
163
135
  return null;
164
- }
165
- if (table.query?.update === null) {
136
+ if (table.query?.update === null)
137
+ return null;
138
+ if (!hasValidPrimaryKey(table))
166
139
  return null;
167
- }
168
- const enumSet = new Set(enumsFromSchemaTypes);
169
140
  const { typeName, singularName } = getTableNames(table);
170
141
  const hookName = getUpdateMutationHookName(table);
171
142
  const mutationName = getUpdateMutationName(table);
172
- const scalarFields = getScalarFields(table);
173
143
  const keysName = `${lcFirst(typeName)}Keys`;
174
144
  const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
175
- const scopeTypeName = `${typeName}Scope`;
145
+ const selectTypeName = `${typeName}Select`;
146
+ const relationTypeName = `${typeName}WithRelations`;
147
+ const patchTypeName = `${typeName}Patch`;
176
148
  const pkFields = getPrimaryKeyInfo(table);
177
149
  const pkField = pkFields[0];
178
- const pkFieldNames = new Set(pkFields.map((pk) => pk.name));
179
- const usedEnums = new Set();
180
- const usedTableTypes = new Set();
181
- for (const field of scalarFields) {
182
- const cleanType = field.type.gqlType.replace(/!/g, '');
183
- if (enumSet.has(cleanType)) {
184
- usedEnums.add(cleanType);
185
- }
186
- else if (tableTypeNames.has(cleanType) && cleanType !== typeName) {
187
- usedTableTypes.add(cleanType);
188
- }
189
- }
190
- const mutationAST = buildUpdateMutationAST({ table });
191
- const mutationDocument = printGraphQL(mutationAST);
150
+ const pkTsType = pkField.tsType === 'string' ? t.tsStringKeyword() : t.tsNumberKeyword();
192
151
  const statements = [];
193
- const reactQueryImport = t.importDeclaration([
194
- t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
195
- t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
196
- ], t.stringLiteral('@tanstack/react-query'));
197
- statements.push(reactQueryImport);
198
- const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
199
- reactQueryTypeImport.importKind = 'type';
200
- statements.push(reactQueryTypeImport);
201
- const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
202
- statements.push(clientImport);
203
- // Import the main type and any other table types used in scalar fields
204
- const allTypesToImportUpdate = [typeName, ...Array.from(usedTableTypes)].sort();
205
- const typesImport = t.importDeclaration(allTypesToImportUpdate.map((t_) => t.importSpecifier(t.identifier(t_), t.identifier(t_))), t.stringLiteral('../types'));
206
- typesImport.importKind = 'type';
207
- statements.push(typesImport);
208
- if (usedEnums.size > 0) {
209
- const enumImport = t.importDeclaration(Array.from(usedEnums).sort().map((e) => t.importSpecifier(t.identifier(e), t.identifier(e))), t.stringLiteral('../schema-types'));
210
- enumImport.importKind = 'type';
211
- statements.push(enumImport);
212
- }
152
+ // Imports
153
+ statements.push(createImportDeclaration('@tanstack/react-query', [
154
+ 'useMutation',
155
+ 'useQueryClient',
156
+ ]));
157
+ statements.push(createImportDeclaration('@tanstack/react-query', ['UseMutationOptions', 'UseMutationResult'], true));
158
+ statements.push(createImportDeclaration('../client', ['getClient']));
159
+ statements.push(createImportDeclaration('../selection', ['buildSelectionArgs']));
160
+ statements.push(createImportDeclaration('../selection', ['SelectionConfig'], true));
213
161
  if (useCentralizedKeys) {
214
- const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
215
- statements.push(queryKeyImport);
216
- if (hasRelationships) {
217
- const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
218
- scopeTypeImport.importKind = 'type';
219
- statements.push(scopeTypeImport);
220
- }
221
- const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
222
- statements.push(mutationKeyImport);
162
+ statements.push(createImportDeclaration('../query-keys', [keysName]));
163
+ statements.push(createImportDeclaration('../mutation-keys', [mutationKeysName]));
223
164
  }
224
- const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
225
- reExportDecl.exportKind = 'type';
226
- statements.push(reExportDecl);
227
- const mutationDocConst = t.variableDeclaration('const', [
228
- t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
165
+ statements.push(createImportDeclaration('../../orm/input-types', [selectTypeName, relationTypeName, patchTypeName], true));
166
+ statements.push(createImportDeclaration('../../orm/select-types', ['InferSelectResult', 'StrictSelect'], true));
167
+ // Re-exports
168
+ statements.push(createTypeReExport([selectTypeName, relationTypeName, patchTypeName], '../../orm/input-types'));
169
+ // Variable type: { pkField: type; patch: PatchType }
170
+ const updateVarType = t.tsTypeLiteral([
171
+ t.tsPropertySignature(t.identifier(pkField.name), t.tsTypeAnnotation(pkTsType)),
172
+ t.tsPropertySignature(t.identifier('patch'), t.tsTypeAnnotation(typeRef(patchTypeName))),
229
173
  ]);
230
- statements.push(t.exportNamedDeclaration(mutationDocConst));
231
- const patchFields = scalarFields
232
- .filter((f) => !pkFieldNames.has(f.name))
233
- .map((f) => {
234
- const prop = t.tsPropertySignature(t.identifier(f.name), t.tsTypeAnnotation(t.tsUnionType([
235
- t.tsTypeReference(t.identifier(fieldTypeToTs(f.type))),
236
- t.tsNullKeyword(),
237
- ])));
238
- prop.optional = true;
239
- return prop;
240
- });
241
- const patchInterface = t.tsInterfaceDeclaration(t.identifier(`${typeName}Patch`), null, null, t.tsInterfaceBody(patchFields));
242
- addJSDocComment(patchInterface, [`Patch type for updating a ${typeName} - all fields optional`]);
243
- statements.push(patchInterface);
244
- const pkTypeAnnotation = pkField.tsType === 'string'
245
- ? t.tsStringKeyword()
246
- : pkField.tsType === 'number'
247
- ? t.tsNumberKeyword()
248
- : t.tsTypeReference(t.identifier(pkField.tsType));
249
- const variablesInterfaceBody = t.tsInterfaceBody([
250
- t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
251
- t.tsPropertySignature(t.identifier(pkField.name), t.tsTypeAnnotation(pkTypeAnnotation)),
252
- t.tsPropertySignature(t.identifier('patch'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${typeName}Patch`)))),
253
- ]))),
174
+ const resultType = (sel) => buildMutationResultType(mutationName, singularName, relationTypeName, sel);
175
+ // Overload 1: with fields
176
+ const o1ParamType = t.tsIntersectionType([
177
+ t.tsTypeLiteral([
178
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(buildFieldsSelectionType(sRef(), selectTypeName))),
179
+ ]),
180
+ useMutationOptionsType(resultType(sRef()), updateVarType),
254
181
  ]);
255
- const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
256
- statements.push(t.exportNamedDeclaration(variablesInterface));
257
- const resultInterfaceBody = t.tsInterfaceBody([
258
- t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([
259
- t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(typeName)))),
260
- ]))),
261
- ]);
262
- const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationResult`), null, null, resultInterfaceBody);
263
- statements.push(t.exportNamedDeclaration(resultInterface));
264
- const hookBodyStatements = [];
265
- hookBodyStatements.push(t.variableDeclaration('const', [
266
- t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
267
- ]));
268
- const mutationOptions = [];
269
- if (useCentralizedKeys) {
270
- mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))));
271
- }
272
- mutationOptions.push(t.objectProperty(t.identifier('mutationFn'), t.arrowFunctionExpression([typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], [
273
- t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationResult`)),
274
- t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)),
275
- ]))));
276
- const detailQueryKey = useCentralizedKeys
277
- ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name))])
278
- : t.arrayExpression([
279
- t.stringLiteral(typeName.toLowerCase()),
280
- t.stringLiteral('detail'),
281
- t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name)),
282
- ]);
283
- const listQueryKey = useCentralizedKeys
284
- ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
285
- : t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
286
- mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([t.identifier('_'), t.identifier('variables')], t.blockStatement([
287
- t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), detailQueryKey)])])),
288
- t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), listQueryKey)])])),
289
- ]))));
290
- mutationOptions.push(t.spreadElement(t.identifier('options')));
291
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
292
- const optionsTypeStr = `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`;
293
- const optionsParam = t.identifier('options');
294
- optionsParam.optional = true;
295
- optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
296
- const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
297
- const hookExport = t.exportNamedDeclaration(hookFunc);
298
- addJSDocComment(hookExport, [
182
+ const o1 = exportDeclareFunction(hookName, createSTypeParam(selectTypeName), [createFunctionParam('params', o1ParamType)], useMutationResultType(resultType(sRef()), updateVarType));
183
+ addJSDocComment(o1, [
299
184
  `Mutation hook for updating a ${typeName}`,
300
185
  '',
301
186
  '@example',
302
187
  '```tsx',
303
- `const { mutate, isPending } = ${hookName}();`,
304
- '',
305
- 'mutate({',
306
- ' input: {',
307
- ` ${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},`,
308
- ' patch: {',
309
- ' // ... fields to update',
310
- ' },',
311
- ' },',
188
+ `const { mutate, isPending } = ${hookName}({`,
189
+ ' selection: { fields: { id: true, name: true } },',
312
190
  '});',
191
+ '',
192
+ `mutate({ ${pkField.name}: 'value-here', patch: { name: 'Updated' } });`,
313
193
  '```',
314
194
  ]);
315
- statements.push(hookExport);
316
- const code = generateCode(statements);
317
- const content = getGeneratedFileHeader(`Update mutation hook for ${typeName}`) + '\n\n' + code;
195
+ statements.push(o1);
196
+ // Implementation
197
+ const implSelProp = t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName))));
198
+ const implParamType = t.tsIntersectionType([
199
+ t.tsTypeLiteral([implSelProp]),
200
+ omitType(typeRef('UseMutationOptions', [
201
+ t.tsAnyKeyword(),
202
+ typeRef('Error'),
203
+ updateVarType,
204
+ ]), ['mutationFn']),
205
+ ]);
206
+ const body = [];
207
+ body.push(buildSelectionArgsCall(selectTypeName));
208
+ body.push(destructureParamsWithSelection('mutationOptions'));
209
+ body.push(voidStatement('_selection'));
210
+ body.push(constDecl('queryClient', callExpr('useQueryClient', [])));
211
+ const mutationKeyExpr = useCentralizedKeys
212
+ ? t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))
213
+ : undefined;
214
+ // mutationFn: ({ pkField, patch }: { pkField: type; patch: PatchType }) =>
215
+ // getClient().singular.update({ where: { pkField }, data: patch, select: ... }).unwrap()
216
+ const destructParam = t.objectPattern([
217
+ shorthandProp(pkField.name),
218
+ shorthandProp('patch'),
219
+ ]);
220
+ destructParam.typeAnnotation = t.tsTypeAnnotation(updateVarType);
221
+ const mutationFnExpr = t.arrowFunctionExpression([destructParam], getClientCallUnwrap(singularName, 'update', t.objectExpression([
222
+ objectProp('where', t.objectExpression([shorthandProp(pkField.name)])),
223
+ objectProp('data', t.identifier('patch')),
224
+ objectProp('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
225
+ ])));
226
+ // onSuccess: invalidate detail and lists
227
+ const detailKeyExpr = useCentralizedKeys
228
+ ? callExpr(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
229
+ t.memberExpression(t.identifier('variables'), t.identifier(pkField.name)),
230
+ ])
231
+ : t.arrayExpression([
232
+ t.stringLiteral(typeName.toLowerCase()),
233
+ t.stringLiteral('detail'),
234
+ t.memberExpression(t.identifier('variables'), t.identifier(pkField.name)),
235
+ ]);
236
+ const listKeyExpr = useCentralizedKeys
237
+ ? callExpr(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
238
+ : t.arrayExpression([
239
+ t.stringLiteral(typeName.toLowerCase()),
240
+ t.stringLiteral('list'),
241
+ ]);
242
+ const onSuccessParam = t.identifier('_');
243
+ const variablesParam = t.identifier('variables');
244
+ const onSuccessFn = t.arrowFunctionExpression([onSuccessParam, variablesParam], t.blockStatement([
245
+ t.expressionStatement(callExpr(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([objectProp('queryKey', detailKeyExpr)])])),
246
+ t.expressionStatement(callExpr(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([objectProp('queryKey', listKeyExpr)])])),
247
+ ]));
248
+ body.push(returnUseMutation(mutationFnExpr, [
249
+ objectProp('onSuccess', onSuccessFn),
250
+ spreadObj(t.identifier('mutationOptions')),
251
+ ], mutationKeyExpr));
252
+ statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
318
253
  return {
319
254
  fileName: getUpdateMutationFileName(table),
320
- content,
255
+ content: generateHookFileCode(`Update mutation hook for ${typeName}`, statements),
321
256
  };
322
257
  }
323
258
  export function generateDeleteMutationHook(table, options = {}) {
324
- const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
325
- if (!reactQueryEnabled) {
259
+ const { reactQueryEnabled = true, useCentralizedKeys = true } = options;
260
+ if (!reactQueryEnabled)
326
261
  return null;
327
- }
328
- if (table.query?.delete === null) {
262
+ if (table.query?.delete === null)
329
263
  return null;
330
- }
331
- const { typeName } = getTableNames(table);
264
+ if (!hasValidPrimaryKey(table))
265
+ return null;
266
+ const { typeName, singularName } = getTableNames(table);
332
267
  const hookName = getDeleteMutationHookName(table);
333
268
  const mutationName = getDeleteMutationName(table);
334
269
  const keysName = `${lcFirst(typeName)}Keys`;
335
270
  const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
336
- const scopeTypeName = `${typeName}Scope`;
271
+ const selectTypeName = `${typeName}Select`;
272
+ const relationTypeName = `${typeName}WithRelations`;
337
273
  const pkFields = getPrimaryKeyInfo(table);
338
274
  const pkField = pkFields[0];
339
- const mutationAST = buildDeleteMutationAST({ table });
340
- const mutationDocument = printGraphQL(mutationAST);
275
+ const pkTsType = pkField.tsType === 'string' ? t.tsStringKeyword() : t.tsNumberKeyword();
341
276
  const statements = [];
342
- const reactQueryImport = t.importDeclaration([
343
- t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
344
- t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
345
- ], t.stringLiteral('@tanstack/react-query'));
346
- statements.push(reactQueryImport);
347
- const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
348
- reactQueryTypeImport.importKind = 'type';
349
- statements.push(reactQueryTypeImport);
350
- const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
351
- statements.push(clientImport);
277
+ // Imports
278
+ statements.push(createImportDeclaration('@tanstack/react-query', [
279
+ 'useMutation',
280
+ 'useQueryClient',
281
+ ]));
282
+ statements.push(createImportDeclaration('@tanstack/react-query', ['UseMutationOptions', 'UseMutationResult'], true));
283
+ statements.push(createImportDeclaration('../client', ['getClient']));
284
+ statements.push(createImportDeclaration('../selection', ['buildSelectionArgs']));
285
+ statements.push(createImportDeclaration('../selection', ['SelectionConfig'], true));
352
286
  if (useCentralizedKeys) {
353
- const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
354
- statements.push(queryKeyImport);
355
- if (hasRelationships) {
356
- const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
357
- scopeTypeImport.importKind = 'type';
358
- statements.push(scopeTypeImport);
359
- }
360
- const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
361
- statements.push(mutationKeyImport);
287
+ statements.push(createImportDeclaration('../query-keys', [keysName]));
288
+ statements.push(createImportDeclaration('../mutation-keys', [mutationKeysName]));
362
289
  }
363
- const mutationDocConst = t.variableDeclaration('const', [
364
- t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
290
+ statements.push(createImportDeclaration('../../orm/input-types', [selectTypeName, relationTypeName], true));
291
+ statements.push(createImportDeclaration('../../orm/select-types', ['InferSelectResult', 'StrictSelect'], true));
292
+ // Re-exports
293
+ statements.push(createTypeReExport([selectTypeName, relationTypeName], '../../orm/input-types'));
294
+ // Variable type: { pkField: type }
295
+ const deleteVarType = t.tsTypeLiteral([
296
+ t.tsPropertySignature(t.identifier(pkField.name), t.tsTypeAnnotation(pkTsType)),
365
297
  ]);
366
- statements.push(t.exportNamedDeclaration(mutationDocConst));
367
- const pkTypeAnnotation = pkField.tsType === 'string'
368
- ? t.tsStringKeyword()
369
- : pkField.tsType === 'number'
370
- ? t.tsNumberKeyword()
371
- : t.tsTypeReference(t.identifier(pkField.tsType));
372
- const variablesInterfaceBody = t.tsInterfaceBody([
373
- t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
374
- t.tsPropertySignature(t.identifier(pkField.name), t.tsTypeAnnotation(pkTypeAnnotation)),
375
- ]))),
298
+ const resultType = (sel) => buildMutationResultType(mutationName, singularName, relationTypeName, sel);
299
+ // Overload 1: with fields
300
+ const o1ParamType = t.tsIntersectionType([
301
+ t.tsTypeLiteral([
302
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(buildFieldsSelectionType(sRef(), selectTypeName))),
303
+ ]),
304
+ useMutationOptionsType(resultType(sRef()), deleteVarType),
376
305
  ]);
377
- const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
378
- statements.push(t.exportNamedDeclaration(variablesInterface));
379
- const clientMutationIdProp = t.tsPropertySignature(t.identifier('clientMutationId'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()])));
380
- const resultInterfaceBody = t.tsInterfaceBody([
381
- t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([clientMutationIdProp]))),
382
- ]);
383
- const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationResult`), null, null, resultInterfaceBody);
384
- statements.push(t.exportNamedDeclaration(resultInterface));
385
- const hookBodyStatements = [];
386
- hookBodyStatements.push(t.variableDeclaration('const', [
387
- t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
388
- ]));
389
- const mutationOptions = [];
390
- if (useCentralizedKeys) {
391
- mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))));
392
- }
393
- mutationOptions.push(t.objectProperty(t.identifier('mutationFn'), t.arrowFunctionExpression([typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], [
394
- t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationResult`)),
395
- t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)),
396
- ]))));
397
- const detailQueryKey = useCentralizedKeys
398
- ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name))])
399
- : t.arrayExpression([
400
- t.stringLiteral(typeName.toLowerCase()),
401
- t.stringLiteral('detail'),
402
- t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name)),
403
- ]);
404
- const listQueryKey = useCentralizedKeys
405
- ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
406
- : t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
407
- mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([t.identifier('_'), t.identifier('variables')], t.blockStatement([
408
- t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('removeQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), detailQueryKey)])])),
409
- t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), listQueryKey)])])),
410
- ]))));
411
- mutationOptions.push(t.spreadElement(t.identifier('options')));
412
- hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
413
- const optionsTypeStr = `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`;
414
- const optionsParam = t.identifier('options');
415
- optionsParam.optional = true;
416
- optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
417
- const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
418
- const hookExport = t.exportNamedDeclaration(hookFunc);
419
- addJSDocComment(hookExport, [
420
- `Mutation hook for deleting a ${typeName}`,
306
+ const o1 = exportDeclareFunction(hookName, createSTypeParam(selectTypeName), [createFunctionParam('params', o1ParamType)], useMutationResultType(resultType(sRef()), deleteVarType));
307
+ addJSDocComment(o1, [
308
+ `Mutation hook for deleting a ${typeName} with typed selection`,
421
309
  '',
422
310
  '@example',
423
311
  '```tsx',
424
- `const { mutate, isPending } = ${hookName}();`,
425
- '',
426
- 'mutate({',
427
- ' input: {',
428
- ` ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},`,
429
- ' },',
312
+ `const { mutate, isPending } = ${hookName}({`,
313
+ ' selection: { fields: { id: true } },',
430
314
  '});',
315
+ '',
316
+ `mutate({ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'} });`,
431
317
  '```',
432
318
  ]);
433
- statements.push(hookExport);
434
- const code = generateCode(statements);
435
- const content = getGeneratedFileHeader(`Delete mutation hook for ${typeName}`) + '\n\n' + code;
319
+ statements.push(o1);
320
+ // Implementation
321
+ const implSelProp = t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(selectionConfigType(typeRef(selectTypeName))));
322
+ const implParamType = t.tsIntersectionType([
323
+ t.tsTypeLiteral([implSelProp]),
324
+ omitType(typeRef('UseMutationOptions', [
325
+ t.tsAnyKeyword(),
326
+ typeRef('Error'),
327
+ deleteVarType,
328
+ ]), ['mutationFn']),
329
+ ]);
330
+ const body = [];
331
+ body.push(buildSelectionArgsCall(selectTypeName));
332
+ body.push(destructureParamsWithSelection('mutationOptions'));
333
+ body.push(voidStatement('_selection'));
334
+ body.push(constDecl('queryClient', callExpr('useQueryClient', [])));
335
+ const mutationKeyExpr = useCentralizedKeys
336
+ ? t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))
337
+ : undefined;
338
+ // mutationFn: ({ pkField }: { pkField: type }) =>
339
+ // getClient().singular.delete({ where: { pkField }, select: ... }).unwrap()
340
+ const destructParam = t.objectPattern([shorthandProp(pkField.name)]);
341
+ destructParam.typeAnnotation = t.tsTypeAnnotation(deleteVarType);
342
+ const mutationFnExpr = t.arrowFunctionExpression([destructParam], getClientCallUnwrap(singularName, 'delete', t.objectExpression([
343
+ objectProp('where', t.objectExpression([shorthandProp(pkField.name)])),
344
+ objectProp('select', t.memberExpression(t.identifier('args'), t.identifier('select'))),
345
+ ])));
346
+ // onSuccess: remove detail, invalidate lists
347
+ const detailKeyExpr = useCentralizedKeys
348
+ ? callExpr(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
349
+ t.memberExpression(t.identifier('variables'), t.identifier(pkField.name)),
350
+ ])
351
+ : t.arrayExpression([
352
+ t.stringLiteral(typeName.toLowerCase()),
353
+ t.stringLiteral('detail'),
354
+ t.memberExpression(t.identifier('variables'), t.identifier(pkField.name)),
355
+ ]);
356
+ const listKeyExpr = useCentralizedKeys
357
+ ? callExpr(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
358
+ : t.arrayExpression([
359
+ t.stringLiteral(typeName.toLowerCase()),
360
+ t.stringLiteral('list'),
361
+ ]);
362
+ const onSuccessFn = t.arrowFunctionExpression([t.identifier('_'), t.identifier('variables')], t.blockStatement([
363
+ t.expressionStatement(callExpr(t.memberExpression(t.identifier('queryClient'), t.identifier('removeQueries')), [t.objectExpression([objectProp('queryKey', detailKeyExpr)])])),
364
+ t.expressionStatement(callExpr(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([objectProp('queryKey', listKeyExpr)])])),
365
+ ]));
366
+ body.push(returnUseMutation(mutationFnExpr, [
367
+ objectProp('onSuccess', onSuccessFn),
368
+ spreadObj(t.identifier('mutationOptions')),
369
+ ], mutationKeyExpr));
370
+ statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
436
371
  return {
437
372
  fileName: getDeleteMutationFileName(table),
438
- content,
373
+ content: generateHookFileCode(`Delete mutation hook for ${typeName}`, statements),
439
374
  };
440
375
  }
441
376
  export function generateAllMutationHooks(tables, options = {}) {