@constructive-io/graphql-codegen 2.23.3 → 2.24.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 (92) hide show
  1. package/README.md +147 -2
  2. package/cli/codegen/babel-ast.d.ts +53 -0
  3. package/cli/codegen/babel-ast.js +160 -0
  4. package/cli/codegen/barrel.d.ts +7 -2
  5. package/cli/codegen/barrel.js +193 -102
  6. package/cli/codegen/client.js +61 -0
  7. package/cli/codegen/custom-mutations.d.ts +2 -12
  8. package/cli/codegen/custom-mutations.js +116 -124
  9. package/cli/codegen/custom-queries.d.ts +2 -10
  10. package/cli/codegen/custom-queries.js +236 -335
  11. package/cli/codegen/gql-ast.js +22 -1
  12. package/cli/codegen/index.d.ts +3 -0
  13. package/cli/codegen/index.js +73 -3
  14. package/cli/codegen/invalidation.d.ts +20 -0
  15. package/cli/codegen/invalidation.js +327 -0
  16. package/cli/codegen/mutation-keys.d.ts +24 -0
  17. package/cli/codegen/mutation-keys.js +247 -0
  18. package/cli/codegen/mutations.d.ts +5 -19
  19. package/cli/codegen/mutations.js +385 -383
  20. package/cli/codegen/orm/barrel.d.ts +1 -1
  21. package/cli/codegen/orm/barrel.js +42 -10
  22. package/cli/codegen/orm/client-generator.d.ts +1 -19
  23. package/cli/codegen/orm/client-generator.js +108 -77
  24. package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  25. package/cli/codegen/orm/custom-ops-generator.js +192 -235
  26. package/cli/codegen/orm/input-types-generator.d.ts +13 -1
  27. package/cli/codegen/orm/input-types-generator.js +425 -147
  28. package/cli/codegen/orm/model-generator.d.ts +1 -19
  29. package/cli/codegen/orm/model-generator.js +229 -234
  30. package/cli/codegen/queries.d.ts +4 -12
  31. package/cli/codegen/queries.js +660 -390
  32. package/cli/codegen/query-keys.d.ts +15 -0
  33. package/cli/codegen/query-keys.js +477 -0
  34. package/cli/codegen/scalars.js +1 -0
  35. package/cli/codegen/schema-types-generator.d.ts +15 -10
  36. package/cli/codegen/schema-types-generator.js +87 -175
  37. package/cli/codegen/type-resolver.d.ts +1 -30
  38. package/cli/codegen/type-resolver.js +0 -53
  39. package/cli/codegen/types.d.ts +1 -1
  40. package/cli/codegen/types.js +76 -21
  41. package/cli/codegen/utils.d.ts +6 -0
  42. package/cli/codegen/utils.js +19 -0
  43. package/esm/cli/codegen/babel-ast.d.ts +53 -0
  44. package/esm/cli/codegen/babel-ast.js +111 -0
  45. package/esm/cli/codegen/barrel.d.ts +7 -2
  46. package/esm/cli/codegen/barrel.js +161 -103
  47. package/esm/cli/codegen/client.js +61 -0
  48. package/esm/cli/codegen/custom-mutations.d.ts +2 -12
  49. package/esm/cli/codegen/custom-mutations.js +83 -124
  50. package/esm/cli/codegen/custom-queries.d.ts +2 -10
  51. package/esm/cli/codegen/custom-queries.js +204 -336
  52. package/esm/cli/codegen/gql-ast.js +23 -2
  53. package/esm/cli/codegen/index.d.ts +3 -0
  54. package/esm/cli/codegen/index.js +69 -2
  55. package/esm/cli/codegen/invalidation.d.ts +20 -0
  56. package/esm/cli/codegen/invalidation.js +291 -0
  57. package/esm/cli/codegen/mutation-keys.d.ts +24 -0
  58. package/esm/cli/codegen/mutation-keys.js +211 -0
  59. package/esm/cli/codegen/mutations.d.ts +5 -19
  60. package/esm/cli/codegen/mutations.js +353 -384
  61. package/esm/cli/codegen/orm/barrel.d.ts +1 -1
  62. package/esm/cli/codegen/orm/barrel.js +10 -11
  63. package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
  64. package/esm/cli/codegen/orm/client-generator.js +76 -78
  65. package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  66. package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
  67. package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
  68. package/esm/cli/codegen/orm/input-types-generator.js +393 -148
  69. package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
  70. package/esm/cli/codegen/orm/model-generator.js +197 -235
  71. package/esm/cli/codegen/queries.d.ts +4 -12
  72. package/esm/cli/codegen/queries.js +628 -391
  73. package/esm/cli/codegen/query-keys.d.ts +15 -0
  74. package/esm/cli/codegen/query-keys.js +441 -0
  75. package/esm/cli/codegen/scalars.js +1 -0
  76. package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
  77. package/esm/cli/codegen/schema-types-generator.js +54 -175
  78. package/esm/cli/codegen/type-resolver.d.ts +1 -30
  79. package/esm/cli/codegen/type-resolver.js +0 -49
  80. package/esm/cli/codegen/types.d.ts +1 -1
  81. package/esm/cli/codegen/types.js +44 -22
  82. package/esm/cli/codegen/utils.d.ts +6 -0
  83. package/esm/cli/codegen/utils.js +18 -0
  84. package/esm/types/config.d.ts +75 -0
  85. package/esm/types/config.js +18 -0
  86. package/package.json +6 -4
  87. package/types/config.d.ts +75 -0
  88. package/types/config.js +19 -1
  89. package/cli/codegen/ts-ast.d.ts +0 -124
  90. package/cli/codegen/ts-ast.js +0 -280
  91. package/esm/cli/codegen/ts-ast.d.ts +0 -124
  92. package/esm/cli/codegen/ts-ast.js +0 -260
@@ -1,475 +1,444 @@
1
- import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
1
+ import * as t from '@babel/types';
2
+ import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } from './babel-ast';
2
3
  import { buildCreateMutationAST, buildUpdateMutationAST, buildDeleteMutationAST, printGraphQL, } from './gql-ast';
3
- import { getTableNames, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, getCreateMutationFileName, getUpdateMutationFileName, getDeleteMutationFileName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getScalarFields, getPrimaryKeyInfo, fieldTypeToTs, ucFirst, lcFirst, } from './utils';
4
- /**
5
- * Check if a field is auto-generated and should be excluded from create inputs
6
- * Uses primary key from constraints and common timestamp patterns
7
- */
4
+ import { getTableNames, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, getCreateMutationFileName, getUpdateMutationFileName, getDeleteMutationFileName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getScalarFields, getPrimaryKeyInfo, fieldTypeToTs, ucFirst, lcFirst, getGeneratedFileHeader, } from './utils';
8
5
  function isAutoGeneratedField(fieldName, pkFieldNames) {
9
6
  const name = fieldName.toLowerCase();
10
- // Exclude primary key fields (from constraints)
11
7
  if (pkFieldNames.has(fieldName))
12
8
  return true;
13
- // Exclude common timestamp patterns (case-insensitive)
14
- // These are typically auto-set by database triggers or defaults
15
9
  const timestampPatterns = [
16
10
  'createdat', 'created_at', 'createddate', 'created_date',
17
11
  'updatedat', 'updated_at', 'updateddate', 'updated_date',
18
- 'deletedat', 'deleted_at', // soft delete timestamps
12
+ 'deletedat', 'deleted_at',
19
13
  ];
20
14
  return timestampPatterns.includes(name);
21
15
  }
22
- // ============================================================================
23
- // Create mutation hook generator
24
- // ============================================================================
25
- /**
26
- * Generate create mutation hook file content using AST
27
- * When reactQueryEnabled is false, returns null since mutations require React Query
28
- */
29
16
  export function generateCreateMutationHook(table, options = {}) {
30
- const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
31
- // Mutations require React Query - skip generation when disabled
17
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [], useCentralizedKeys = true, hasRelationships = false, tableTypeNames = new Set(), } = options;
32
18
  if (!reactQueryEnabled) {
33
19
  return null;
34
20
  }
35
21
  const enumSet = new Set(enumsFromSchemaTypes);
36
- const project = createProject();
37
22
  const { typeName, singularName } = getTableNames(table);
38
23
  const hookName = getCreateMutationHookName(table);
24
+ const keysName = `${lcFirst(typeName)}Keys`;
25
+ const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
26
+ const scopeTypeName = `${typeName}Scope`;
39
27
  const mutationName = getCreateMutationName(table);
40
28
  const scalarFields = getScalarFields(table);
41
- // Get primary key field names dynamically from table constraints
42
- const pkFieldNames = new Set(getPrimaryKeyInfo(table).map(pk => pk.name));
43
- // Collect which enums are used by this table's fields
29
+ const pkFieldNames = new Set(getPrimaryKeyInfo(table).map((pk) => pk.name));
44
30
  const usedEnums = new Set();
31
+ const usedTableTypes = new Set();
45
32
  for (const field of scalarFields) {
46
33
  const cleanType = field.type.gqlType.replace(/!/g, '');
47
34
  if (enumSet.has(cleanType)) {
48
35
  usedEnums.add(cleanType);
49
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
+ }
50
41
  }
51
- // Generate GraphQL document via AST
52
42
  const mutationAST = buildCreateMutationAST({ table });
53
43
  const mutationDocument = printGraphQL(mutationAST);
54
- const sourceFile = createSourceFile(project, getCreateMutationFileName(table));
55
- // Add file header
56
- sourceFile.insertText(0, createFileHeader(`Create mutation hook for ${typeName}`) + '\n\n');
57
- // Build import declarations
58
- const imports = [
59
- createImport({
60
- moduleSpecifier: '@tanstack/react-query',
61
- namedImports: ['useMutation', 'useQueryClient'],
62
- typeOnlyNamedImports: ['UseMutationOptions'],
63
- }),
64
- createImport({
65
- moduleSpecifier: '../client',
66
- namedImports: ['execute'],
67
- }),
68
- createImport({
69
- moduleSpecifier: '../types',
70
- typeOnlyNamedImports: [typeName],
71
- }),
72
- ];
73
- // Add import for enum types from schema-types if any are used
44
+ 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);
74
60
  if (usedEnums.size > 0) {
75
- imports.push(createImport({
76
- moduleSpecifier: '../schema-types',
77
- typeOnlyNamedImports: Array.from(usedEnums).sort(),
78
- }));
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);
79
75
  }
80
- // Add imports
81
- sourceFile.addImportDeclarations(imports);
82
- // Re-export entity type
83
- sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
84
- // Add section comment
85
- sourceFile.addStatements('\n// ============================================================================');
86
- sourceFile.addStatements('// GraphQL Document');
87
- sourceFile.addStatements('// ============================================================================\n');
88
- // Add mutation document constant
89
- sourceFile.addVariableStatement(createConst(`${mutationName}MutationDocument`, '`\n' + mutationDocument + '`'));
90
- // Add section comment
91
- sourceFile.addStatements('\n// ============================================================================');
92
- sourceFile.addStatements('// Types');
93
- sourceFile.addStatements('// ============================================================================\n');
94
- // Generate CreateInput type - exclude auto-generated fields
95
- // Note: Not exported to avoid conflicts with schema-types
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));
96
83
  const inputFields = scalarFields
97
84
  .filter((f) => !isAutoGeneratedField(f.name, pkFieldNames))
98
- .map((f) => ({
99
- name: f.name,
100
- type: `${fieldTypeToTs(f.type)} | null`,
101
- optional: true,
102
- }));
103
- sourceFile.addInterface(createInterface(`${typeName}CreateInput`, inputFields, {
104
- docs: [`Input type for creating a ${typeName}`],
105
- isExported: false,
106
- }));
107
- // Variables interface
108
- sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
109
- {
110
- name: 'input',
111
- type: `{
112
- ${lcFirst(typeName)}: ${typeName}CreateInput;
113
- }`,
114
- },
115
- ]));
116
- // Result interface
117
- sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationResult`, [
118
- {
119
- name: mutationName,
120
- type: `{
121
- ${singularName}: ${typeName};
122
- }`,
123
- },
124
- ]));
125
- // Add section comment
126
- sourceFile.addStatements('\n// ============================================================================');
127
- sourceFile.addStatements('// Hook');
128
- sourceFile.addStatements('// ============================================================================\n');
129
- // Hook function
130
- sourceFile.addFunction({
131
- name: hookName,
132
- isExported: true,
133
- parameters: [
134
- {
135
- name: 'options',
136
- type: `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`,
137
- hasQuestionToken: true,
138
- },
139
- ],
140
- statements: `const queryClient = useQueryClient();
141
-
142
- return useMutation({
143
- mutationFn: (variables: ${ucFirst(mutationName)}MutationVariables) =>
144
- execute<${ucFirst(mutationName)}MutationResult, ${ucFirst(mutationName)}MutationVariables>(
145
- ${mutationName}MutationDocument,
146
- variables
147
- ),
148
- onSuccess: () => {
149
- // Invalidate list queries to refetch
150
- queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
151
- },
152
- ...options,
153
- });`,
154
- docs: [
155
- {
156
- description: `Mutation hook for creating a ${typeName}
157
-
158
- @example
159
- \`\`\`tsx
160
- const { mutate, isPending } = ${hookName}();
161
-
162
- mutate({
163
- input: {
164
- ${lcFirst(typeName)}: {
165
- // ... fields
166
- },
167
- },
168
- });
169
- \`\`\``,
170
- },
171
- ],
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;
172
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'), [])),
113
+ ]));
114
+ const mutationOptions = [];
115
+ if (useCentralizedKeys) {
116
+ mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.callExpression(t.memberExpression(t.identifier(mutationKeysName), t.identifier('create')), [])));
117
+ }
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, [
137
+ `Mutation hook for creating a ${typeName}`,
138
+ '',
139
+ '@example',
140
+ '```tsx',
141
+ `const { mutate, isPending } = ${hookName}();`,
142
+ '',
143
+ 'mutate({',
144
+ ' input: {',
145
+ ` ${lcFirst(typeName)}: {`,
146
+ ' // ... fields',
147
+ ' },',
148
+ ' },',
149
+ '});',
150
+ '```',
151
+ ]);
152
+ statements.push(hookExport);
153
+ const code = generateCode(statements);
154
+ const content = getGeneratedFileHeader(`Create mutation hook for ${typeName}`) + '\n\n' + code;
173
155
  return {
174
156
  fileName: getCreateMutationFileName(table),
175
- content: getFormattedOutput(sourceFile),
157
+ content,
176
158
  };
177
159
  }
178
- // ============================================================================
179
- // Update mutation hook generator
180
- // ============================================================================
181
- /**
182
- * Generate update mutation hook file content using AST
183
- * When reactQueryEnabled is false, returns null since mutations require React Query
184
- */
185
160
  export function generateUpdateMutationHook(table, options = {}) {
186
- const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
187
- // Mutations require React Query - skip generation when disabled
161
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [], useCentralizedKeys = true, hasRelationships = false, tableTypeNames = new Set(), } = options;
188
162
  if (!reactQueryEnabled) {
189
163
  return null;
190
164
  }
191
- // Check if update mutation exists
192
165
  if (table.query?.update === null) {
193
166
  return null;
194
167
  }
195
168
  const enumSet = new Set(enumsFromSchemaTypes);
196
- const project = createProject();
197
169
  const { typeName, singularName } = getTableNames(table);
198
170
  const hookName = getUpdateMutationHookName(table);
199
171
  const mutationName = getUpdateMutationName(table);
200
172
  const scalarFields = getScalarFields(table);
201
- // Get primary key info dynamically from table constraints
173
+ const keysName = `${lcFirst(typeName)}Keys`;
174
+ const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
175
+ const scopeTypeName = `${typeName}Scope`;
202
176
  const pkFields = getPrimaryKeyInfo(table);
203
- const pkField = pkFields[0]; // Use first PK field
204
- const pkFieldNames = new Set(pkFields.map(pk => pk.name));
205
- // Collect which enums are used by this table's fields
177
+ const pkField = pkFields[0];
178
+ const pkFieldNames = new Set(pkFields.map((pk) => pk.name));
206
179
  const usedEnums = new Set();
180
+ const usedTableTypes = new Set();
207
181
  for (const field of scalarFields) {
208
182
  const cleanType = field.type.gqlType.replace(/!/g, '');
209
183
  if (enumSet.has(cleanType)) {
210
184
  usedEnums.add(cleanType);
211
185
  }
186
+ else if (tableTypeNames.has(cleanType) && cleanType !== typeName) {
187
+ usedTableTypes.add(cleanType);
188
+ }
212
189
  }
213
- // Generate GraphQL document via AST
214
190
  const mutationAST = buildUpdateMutationAST({ table });
215
191
  const mutationDocument = printGraphQL(mutationAST);
216
- const sourceFile = createSourceFile(project, getUpdateMutationFileName(table));
217
- // Add file header
218
- sourceFile.insertText(0, createFileHeader(`Update mutation hook for ${typeName}`) + '\n\n');
219
- // Build import declarations
220
- const imports = [
221
- createImport({
222
- moduleSpecifier: '@tanstack/react-query',
223
- namedImports: ['useMutation', 'useQueryClient'],
224
- typeOnlyNamedImports: ['UseMutationOptions'],
225
- }),
226
- createImport({
227
- moduleSpecifier: '../client',
228
- namedImports: ['execute'],
229
- }),
230
- createImport({
231
- moduleSpecifier: '../types',
232
- typeOnlyNamedImports: [typeName],
233
- }),
234
- ];
235
- // Add import for enum types from schema-types if any are used
192
+ 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);
236
208
  if (usedEnums.size > 0) {
237
- imports.push(createImport({
238
- moduleSpecifier: '../schema-types',
239
- typeOnlyNamedImports: Array.from(usedEnums).sort(),
240
- }));
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
+ }
213
+ 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);
241
223
  }
242
- // Add imports
243
- sourceFile.addImportDeclarations(imports);
244
- // Re-export entity type
245
- sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
246
- // Add section comment
247
- sourceFile.addStatements('\n// ============================================================================');
248
- sourceFile.addStatements('// GraphQL Document');
249
- sourceFile.addStatements('// ============================================================================\n');
250
- // Add mutation document constant
251
- sourceFile.addVariableStatement(createConst(`${mutationName}MutationDocument`, '`\n' + mutationDocument + '`'));
252
- // Add section comment
253
- sourceFile.addStatements('\n// ============================================================================');
254
- sourceFile.addStatements('// Types');
255
- sourceFile.addStatements('// ============================================================================\n');
256
- // Generate Patch type - all fields optional, exclude primary key
257
- // Note: Not exported to avoid conflicts with schema-types
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)], [])),
229
+ ]);
230
+ statements.push(t.exportNamedDeclaration(mutationDocConst));
258
231
  const patchFields = scalarFields
259
232
  .filter((f) => !pkFieldNames.has(f.name))
260
- .map((f) => ({
261
- name: f.name,
262
- type: `${fieldTypeToTs(f.type)} | null`,
263
- optional: true,
264
- }));
265
- sourceFile.addInterface(createInterface(`${typeName}Patch`, patchFields, {
266
- docs: [`Patch type for updating a ${typeName} - all fields optional`],
267
- isExported: false,
268
- }));
269
- // Variables interface - use dynamic PK field name and type
270
- sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
271
- {
272
- name: 'input',
273
- type: `{
274
- ${pkField.name}: ${pkField.tsType};
275
- patch: ${typeName}Patch;
276
- }`,
277
- },
278
- ]));
279
- // Result interface
280
- sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationResult`, [
281
- {
282
- name: mutationName,
283
- type: `{
284
- ${singularName}: ${typeName};
285
- }`,
286
- },
287
- ]));
288
- // Add section comment
289
- sourceFile.addStatements('\n// ============================================================================');
290
- sourceFile.addStatements('// Hook');
291
- sourceFile.addStatements('// ============================================================================\n');
292
- // Hook function
293
- sourceFile.addFunction({
294
- name: hookName,
295
- isExported: true,
296
- parameters: [
297
- {
298
- name: 'options',
299
- type: `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`,
300
- hasQuestionToken: true,
301
- },
302
- ],
303
- statements: `const queryClient = useQueryClient();
304
-
305
- return useMutation({
306
- mutationFn: (variables: ${ucFirst(mutationName)}MutationVariables) =>
307
- execute<${ucFirst(mutationName)}MutationResult, ${ucFirst(mutationName)}MutationVariables>(
308
- ${mutationName}MutationDocument,
309
- variables
310
- ),
311
- onSuccess: (_, variables) => {
312
- // Invalidate specific item and list queries
313
- queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
314
- queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
315
- },
316
- ...options,
317
- });`,
318
- docs: [
319
- {
320
- description: `Mutation hook for updating a ${typeName}
321
-
322
- @example
323
- \`\`\`tsx
324
- const { mutate, isPending } = ${hookName}();
325
-
326
- mutate({
327
- input: {
328
- ${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},
329
- patch: {
330
- // ... fields to update
331
- },
332
- },
333
- });
334
- \`\`\``,
335
- },
336
- ],
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;
337
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
+ ]))),
254
+ ]);
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, [
299
+ `Mutation hook for updating a ${typeName}`,
300
+ '',
301
+ '@example',
302
+ '```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
+ ' },',
312
+ '});',
313
+ '```',
314
+ ]);
315
+ statements.push(hookExport);
316
+ const code = generateCode(statements);
317
+ const content = getGeneratedFileHeader(`Update mutation hook for ${typeName}`) + '\n\n' + code;
338
318
  return {
339
319
  fileName: getUpdateMutationFileName(table),
340
- content: getFormattedOutput(sourceFile),
320
+ content,
341
321
  };
342
322
  }
343
- // ============================================================================
344
- // Delete mutation hook generator
345
- // ============================================================================
346
- /**
347
- * Generate delete mutation hook file content using AST
348
- * When reactQueryEnabled is false, returns null since mutations require React Query
349
- */
350
323
  export function generateDeleteMutationHook(table, options = {}) {
351
- const { reactQueryEnabled = true } = options;
352
- // Mutations require React Query - skip generation when disabled
324
+ const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
353
325
  if (!reactQueryEnabled) {
354
326
  return null;
355
327
  }
356
- // Check if delete mutation exists
357
328
  if (table.query?.delete === null) {
358
329
  return null;
359
330
  }
360
- const project = createProject();
361
331
  const { typeName } = getTableNames(table);
362
332
  const hookName = getDeleteMutationHookName(table);
363
333
  const mutationName = getDeleteMutationName(table);
364
- // Get primary key info dynamically from table constraints
334
+ const keysName = `${lcFirst(typeName)}Keys`;
335
+ const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
336
+ const scopeTypeName = `${typeName}Scope`;
365
337
  const pkFields = getPrimaryKeyInfo(table);
366
- const pkField = pkFields[0]; // Use first PK field
367
- // Generate GraphQL document via AST
338
+ const pkField = pkFields[0];
368
339
  const mutationAST = buildDeleteMutationAST({ table });
369
340
  const mutationDocument = printGraphQL(mutationAST);
370
- const sourceFile = createSourceFile(project, getDeleteMutationFileName(table));
371
- // Add file header
372
- sourceFile.insertText(0, createFileHeader(`Delete mutation hook for ${typeName}`) + '\n\n');
373
- // Add imports
374
- sourceFile.addImportDeclarations([
375
- createImport({
376
- moduleSpecifier: '@tanstack/react-query',
377
- namedImports: ['useMutation', 'useQueryClient'],
378
- typeOnlyNamedImports: ['UseMutationOptions'],
379
- }),
380
- createImport({
381
- moduleSpecifier: '../client',
382
- namedImports: ['execute'],
383
- }),
341
+ 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);
352
+ 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);
362
+ }
363
+ const mutationDocConst = t.variableDeclaration('const', [
364
+ t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
384
365
  ]);
385
- // Add section comment
386
- sourceFile.addStatements('\n// ============================================================================');
387
- sourceFile.addStatements('// GraphQL Document');
388
- sourceFile.addStatements('// ============================================================================\n');
389
- // Add mutation document constant
390
- sourceFile.addVariableStatement(createConst(`${mutationName}MutationDocument`, '`\n' + mutationDocument + '`'));
391
- // Add section comment
392
- sourceFile.addStatements('\n// ============================================================================');
393
- sourceFile.addStatements('// Types');
394
- sourceFile.addStatements('// ============================================================================\n');
395
- // Variables interface - use dynamic PK field name and type
396
- sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
397
- {
398
- name: 'input',
399
- type: `{
400
- ${pkField.name}: ${pkField.tsType};
401
- }`,
402
- },
403
- ]));
404
- // Result interface
405
- sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationResult`, [
406
- {
407
- name: mutationName,
408
- type: `{
409
- clientMutationId: string | null;
410
- deleted${ucFirst(pkField.name)}: ${pkField.tsType} | null;
411
- }`,
412
- },
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
+ ]))),
376
+ ]);
377
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
378
+ statements.push(t.exportNamedDeclaration(variablesInterface));
379
+ const deletedPkProp = t.tsPropertySignature(t.identifier(`deleted${ucFirst(pkField.name)}`), t.tsTypeAnnotation(t.tsUnionType([pkTypeAnnotation, t.tsNullKeyword()])));
380
+ const clientMutationIdProp = t.tsPropertySignature(t.identifier('clientMutationId'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()])));
381
+ const resultInterfaceBody = t.tsInterfaceBody([
382
+ t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([clientMutationIdProp, deletedPkProp]))),
383
+ ]);
384
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationResult`), null, null, resultInterfaceBody);
385
+ statements.push(t.exportNamedDeclaration(resultInterface));
386
+ const hookBodyStatements = [];
387
+ hookBodyStatements.push(t.variableDeclaration('const', [
388
+ t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
413
389
  ]));
414
- // Add section comment
415
- sourceFile.addStatements('\n// ============================================================================');
416
- sourceFile.addStatements('// Hook');
417
- sourceFile.addStatements('// ============================================================================\n');
418
- // Hook function
419
- sourceFile.addFunction({
420
- name: hookName,
421
- isExported: true,
422
- parameters: [
423
- {
424
- name: 'options',
425
- type: `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`,
426
- hasQuestionToken: true,
427
- },
428
- ],
429
- statements: `const queryClient = useQueryClient();
430
-
431
- return useMutation({
432
- mutationFn: (variables: ${ucFirst(mutationName)}MutationVariables) =>
433
- execute<${ucFirst(mutationName)}MutationResult, ${ucFirst(mutationName)}MutationVariables>(
434
- ${mutationName}MutationDocument,
435
- variables
436
- ),
437
- onSuccess: (_, variables) => {
438
- // Remove from cache and invalidate list
439
- queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
440
- queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
441
- },
442
- ...options,
443
- });`,
444
- docs: [
445
- {
446
- description: `Mutation hook for deleting a ${typeName}
447
-
448
- @example
449
- \`\`\`tsx
450
- const { mutate, isPending } = ${hookName}();
451
-
452
- mutate({
453
- input: {
454
- ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},
455
- },
456
- });
457
- \`\`\``,
458
- },
459
- ],
460
- });
390
+ const mutationOptions = [];
391
+ if (useCentralizedKeys) {
392
+ mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))));
393
+ }
394
+ 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')], [
395
+ t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationResult`)),
396
+ t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)),
397
+ ]))));
398
+ const detailQueryKey = useCentralizedKeys
399
+ ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name))])
400
+ : t.arrayExpression([
401
+ t.stringLiteral(typeName.toLowerCase()),
402
+ t.stringLiteral('detail'),
403
+ t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name)),
404
+ ]);
405
+ const listQueryKey = useCentralizedKeys
406
+ ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
407
+ : t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
408
+ mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([t.identifier('_'), t.identifier('variables')], t.blockStatement([
409
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('removeQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), detailQueryKey)])])),
410
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), listQueryKey)])])),
411
+ ]))));
412
+ mutationOptions.push(t.spreadElement(t.identifier('options')));
413
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
414
+ const optionsTypeStr = `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`;
415
+ const optionsParam = t.identifier('options');
416
+ optionsParam.optional = true;
417
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
418
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
419
+ const hookExport = t.exportNamedDeclaration(hookFunc);
420
+ addJSDocComment(hookExport, [
421
+ `Mutation hook for deleting a ${typeName}`,
422
+ '',
423
+ '@example',
424
+ '```tsx',
425
+ `const { mutate, isPending } = ${hookName}();`,
426
+ '',
427
+ 'mutate({',
428
+ ' input: {',
429
+ ` ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},`,
430
+ ' },',
431
+ '});',
432
+ '```',
433
+ ]);
434
+ statements.push(hookExport);
435
+ const code = generateCode(statements);
436
+ const content = getGeneratedFileHeader(`Delete mutation hook for ${typeName}`) + '\n\n' + code;
461
437
  return {
462
438
  fileName: getDeleteMutationFileName(table),
463
- content: getFormattedOutput(sourceFile),
439
+ content,
464
440
  };
465
441
  }
466
- // ============================================================================
467
- // Batch generator
468
- // ============================================================================
469
- /**
470
- * Generate all mutation hook files for all tables
471
- * When reactQueryEnabled is false, returns empty array since mutations require React Query
472
- */
473
442
  export function generateAllMutationHooks(tables, options = {}) {
474
443
  const files = [];
475
444
  for (const table of tables) {