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