@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,481 +1,483 @@
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, tableTypeNames = new Set(), } = 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();
70
+ const usedTableTypes = new Set();
51
71
  for (const field of scalarFields) {
52
72
  const cleanType = field.type.gqlType.replace(/!/g, '');
53
73
  if (enumSet.has(cleanType)) {
54
74
  usedEnums.add(cleanType);
55
75
  }
76
+ else if (tableTypeNames.has(cleanType) && cleanType !== typeName) {
77
+ // Track table types used in scalar fields (excluding the main type which is already imported)
78
+ usedTableTypes.add(cleanType);
79
+ }
56
80
  }
57
- // Generate GraphQL document via AST
58
81
  const mutationAST = (0, gql_ast_1.buildCreateMutationAST)({ table });
59
82
  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
83
+ const statements = [];
84
+ const reactQueryImport = t.importDeclaration([
85
+ t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
86
+ t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
87
+ ], t.stringLiteral('@tanstack/react-query'));
88
+ statements.push(reactQueryImport);
89
+ const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
90
+ reactQueryTypeImport.importKind = 'type';
91
+ statements.push(reactQueryTypeImport);
92
+ const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
93
+ statements.push(clientImport);
94
+ // Import the main type and any other table types used in scalar fields
95
+ const allTypesToImport = [typeName, ...Array.from(usedTableTypes)].sort();
96
+ const typesImport = t.importDeclaration(allTypesToImport.map((t_) => t.importSpecifier(t.identifier(t_), t.identifier(t_))), t.stringLiteral('../types'));
97
+ typesImport.importKind = 'type';
98
+ statements.push(typesImport);
80
99
  if (usedEnums.size > 0) {
81
- imports.push((0, ts_ast_1.createImport)({
82
- moduleSpecifier: '../schema-types',
83
- typeOnlyNamedImports: Array.from(usedEnums).sort(),
84
- }));
100
+ const enumImport = t.importDeclaration(Array.from(usedEnums).sort().map((e) => t.importSpecifier(t.identifier(e), t.identifier(e))), t.stringLiteral('../schema-types'));
101
+ enumImport.importKind = 'type';
102
+ statements.push(enumImport);
103
+ }
104
+ if (useCentralizedKeys) {
105
+ const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
106
+ statements.push(queryKeyImport);
107
+ if (hasRelationships) {
108
+ const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
109
+ scopeTypeImport.importKind = 'type';
110
+ statements.push(scopeTypeImport);
111
+ }
112
+ const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
113
+ statements.push(mutationKeyImport);
85
114
  }
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
115
+ const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
116
+ reExportDecl.exportKind = 'type';
117
+ statements.push(reExportDecl);
118
+ const mutationDocConst = t.variableDeclaration('const', [
119
+ t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
120
+ ]);
121
+ statements.push(t.exportNamedDeclaration(mutationDocConst));
102
122
  const inputFields = scalarFields
103
123
  .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
- ],
124
+ .map((f) => {
125
+ const prop = t.tsPropertySignature(t.identifier(f.name), t.tsTypeAnnotation(t.tsUnionType([
126
+ t.tsTypeReference(t.identifier((0, utils_1.fieldTypeToTs)(f.type))),
127
+ t.tsNullKeyword(),
128
+ ])));
129
+ prop.optional = true;
130
+ return prop;
178
131
  });
132
+ const createInputInterface = t.tsInterfaceDeclaration(t.identifier(`${typeName}CreateInput`), null, null, t.tsInterfaceBody(inputFields));
133
+ (0, babel_ast_1.addJSDocComment)(createInputInterface, [`Input type for creating a ${typeName}`]);
134
+ statements.push(createInputInterface);
135
+ const variablesInterfaceBody = t.tsInterfaceBody([
136
+ t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
137
+ t.tsPropertySignature(t.identifier((0, utils_1.lcFirst)(typeName)), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${typeName}CreateInput`)))),
138
+ ]))),
139
+ ]);
140
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
141
+ statements.push(t.exportNamedDeclaration(variablesInterface));
142
+ const resultInterfaceBody = t.tsInterfaceBody([
143
+ t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([
144
+ t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(typeName)))),
145
+ ]))),
146
+ ]);
147
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationResult`), null, null, resultInterfaceBody);
148
+ statements.push(t.exportNamedDeclaration(resultInterface));
149
+ const hookBodyStatements = [];
150
+ hookBodyStatements.push(t.variableDeclaration('const', [
151
+ t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
152
+ ]));
153
+ const mutationOptions = [];
154
+ if (useCentralizedKeys) {
155
+ mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.callExpression(t.memberExpression(t.identifier(mutationKeysName), t.identifier('create')), [])));
156
+ }
157
+ 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`)))], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], [
158
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationResult`)),
159
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`)),
160
+ ]))));
161
+ const invalidateQueryKey = useCentralizedKeys
162
+ ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
163
+ : t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
164
+ mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([], t.blockStatement([
165
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), invalidateQueryKey)])])),
166
+ ]))));
167
+ mutationOptions.push(t.spreadElement(t.identifier('options')));
168
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
169
+ const optionsTypeStr = `Omit<UseMutationOptions<${(0, utils_1.ucFirst)(mutationName)}MutationResult, Error, ${(0, utils_1.ucFirst)(mutationName)}MutationVariables>, 'mutationFn'>`;
170
+ const optionsParam = t.identifier('options');
171
+ optionsParam.optional = true;
172
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
173
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
174
+ const hookExport = t.exportNamedDeclaration(hookFunc);
175
+ (0, babel_ast_1.addJSDocComment)(hookExport, [
176
+ `Mutation hook for creating a ${typeName}`,
177
+ '',
178
+ '@example',
179
+ '```tsx',
180
+ `const { mutate, isPending } = ${hookName}();`,
181
+ '',
182
+ 'mutate({',
183
+ ' input: {',
184
+ ` ${(0, utils_1.lcFirst)(typeName)}: {`,
185
+ ' // ... fields',
186
+ ' },',
187
+ ' },',
188
+ '});',
189
+ '```',
190
+ ]);
191
+ statements.push(hookExport);
192
+ const code = (0, babel_ast_1.generateCode)(statements);
193
+ const content = (0, utils_1.getGeneratedFileHeader)(`Create mutation hook for ${typeName}`) + '\n\n' + code;
179
194
  return {
180
195
  fileName: (0, utils_1.getCreateMutationFileName)(table),
181
- content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
196
+ content,
182
197
  };
183
198
  }
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
199
  function generateUpdateMutationHook(table, options = {}) {
192
- const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
193
- // Mutations require React Query - skip generation when disabled
200
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [], useCentralizedKeys = true, hasRelationships = false, tableTypeNames = new Set(), } = options;
194
201
  if (!reactQueryEnabled) {
195
202
  return null;
196
203
  }
197
- // Check if update mutation exists
198
204
  if (table.query?.update === null) {
199
205
  return null;
200
206
  }
201
207
  const enumSet = new Set(enumsFromSchemaTypes);
202
- const project = (0, ts_ast_1.createProject)();
203
208
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
204
209
  const hookName = (0, utils_1.getUpdateMutationHookName)(table);
205
210
  const mutationName = (0, utils_1.getUpdateMutationName)(table);
206
211
  const scalarFields = (0, utils_1.getScalarFields)(table);
207
- // Get primary key info dynamically from table constraints
212
+ const keysName = `${(0, utils_1.lcFirst)(typeName)}Keys`;
213
+ const mutationKeysName = `${(0, utils_1.lcFirst)(typeName)}MutationKeys`;
214
+ const scopeTypeName = `${typeName}Scope`;
208
215
  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
216
+ const pkField = pkFields[0];
217
+ const pkFieldNames = new Set(pkFields.map((pk) => pk.name));
212
218
  const usedEnums = new Set();
219
+ const usedTableTypes = new Set();
213
220
  for (const field of scalarFields) {
214
221
  const cleanType = field.type.gqlType.replace(/!/g, '');
215
222
  if (enumSet.has(cleanType)) {
216
223
  usedEnums.add(cleanType);
217
224
  }
225
+ else if (tableTypeNames.has(cleanType) && cleanType !== typeName) {
226
+ usedTableTypes.add(cleanType);
227
+ }
218
228
  }
219
- // Generate GraphQL document via AST
220
229
  const mutationAST = (0, gql_ast_1.buildUpdateMutationAST)({ table });
221
230
  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
231
+ const statements = [];
232
+ const reactQueryImport = t.importDeclaration([
233
+ t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
234
+ t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
235
+ ], t.stringLiteral('@tanstack/react-query'));
236
+ statements.push(reactQueryImport);
237
+ const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
238
+ reactQueryTypeImport.importKind = 'type';
239
+ statements.push(reactQueryTypeImport);
240
+ const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
241
+ statements.push(clientImport);
242
+ // Import the main type and any other table types used in scalar fields
243
+ const allTypesToImportUpdate = [typeName, ...Array.from(usedTableTypes)].sort();
244
+ const typesImport = t.importDeclaration(allTypesToImportUpdate.map((t_) => t.importSpecifier(t.identifier(t_), t.identifier(t_))), t.stringLiteral('../types'));
245
+ typesImport.importKind = 'type';
246
+ statements.push(typesImport);
242
247
  if (usedEnums.size > 0) {
243
- imports.push((0, ts_ast_1.createImport)({
244
- moduleSpecifier: '../schema-types',
245
- typeOnlyNamedImports: Array.from(usedEnums).sort(),
246
- }));
248
+ const enumImport = t.importDeclaration(Array.from(usedEnums).sort().map((e) => t.importSpecifier(t.identifier(e), t.identifier(e))), t.stringLiteral('../schema-types'));
249
+ enumImport.importKind = 'type';
250
+ statements.push(enumImport);
247
251
  }
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
252
+ if (useCentralizedKeys) {
253
+ const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
254
+ statements.push(queryKeyImport);
255
+ if (hasRelationships) {
256
+ const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
257
+ scopeTypeImport.importKind = 'type';
258
+ statements.push(scopeTypeImport);
259
+ }
260
+ const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
261
+ statements.push(mutationKeyImport);
262
+ }
263
+ const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
264
+ reExportDecl.exportKind = 'type';
265
+ statements.push(reExportDecl);
266
+ const mutationDocConst = t.variableDeclaration('const', [
267
+ t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
268
+ ]);
269
+ statements.push(t.exportNamedDeclaration(mutationDocConst));
264
270
  const patchFields = scalarFields
265
271
  .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
- ],
272
+ .map((f) => {
273
+ const prop = t.tsPropertySignature(t.identifier(f.name), t.tsTypeAnnotation(t.tsUnionType([
274
+ t.tsTypeReference(t.identifier((0, utils_1.fieldTypeToTs)(f.type))),
275
+ t.tsNullKeyword(),
276
+ ])));
277
+ prop.optional = true;
278
+ return prop;
343
279
  });
280
+ const patchInterface = t.tsInterfaceDeclaration(t.identifier(`${typeName}Patch`), null, null, t.tsInterfaceBody(patchFields));
281
+ (0, babel_ast_1.addJSDocComment)(patchInterface, [`Patch type for updating a ${typeName} - all fields optional`]);
282
+ statements.push(patchInterface);
283
+ const pkTypeAnnotation = pkField.tsType === 'string'
284
+ ? t.tsStringKeyword()
285
+ : pkField.tsType === 'number'
286
+ ? t.tsNumberKeyword()
287
+ : t.tsTypeReference(t.identifier(pkField.tsType));
288
+ const variablesInterfaceBody = t.tsInterfaceBody([
289
+ t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
290
+ t.tsPropertySignature(t.identifier(pkField.name), t.tsTypeAnnotation(pkTypeAnnotation)),
291
+ t.tsPropertySignature(t.identifier('patch'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${typeName}Patch`)))),
292
+ ]))),
293
+ ]);
294
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
295
+ statements.push(t.exportNamedDeclaration(variablesInterface));
296
+ const resultInterfaceBody = t.tsInterfaceBody([
297
+ t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([
298
+ t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(typeName)))),
299
+ ]))),
300
+ ]);
301
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationResult`), null, null, resultInterfaceBody);
302
+ statements.push(t.exportNamedDeclaration(resultInterface));
303
+ const hookBodyStatements = [];
304
+ hookBodyStatements.push(t.variableDeclaration('const', [
305
+ t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
306
+ ]));
307
+ const mutationOptions = [];
308
+ if (useCentralizedKeys) {
309
+ mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))));
310
+ }
311
+ 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`)))], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], [
312
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationResult`)),
313
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`)),
314
+ ]))));
315
+ const detailQueryKey = useCentralizedKeys
316
+ ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name))])
317
+ : t.arrayExpression([
318
+ t.stringLiteral(typeName.toLowerCase()),
319
+ t.stringLiteral('detail'),
320
+ t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name)),
321
+ ]);
322
+ const listQueryKey = useCentralizedKeys
323
+ ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
324
+ : t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
325
+ mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([t.identifier('_'), t.identifier('variables')], t.blockStatement([
326
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), detailQueryKey)])])),
327
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), listQueryKey)])])),
328
+ ]))));
329
+ mutationOptions.push(t.spreadElement(t.identifier('options')));
330
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
331
+ const optionsTypeStr = `Omit<UseMutationOptions<${(0, utils_1.ucFirst)(mutationName)}MutationResult, Error, ${(0, utils_1.ucFirst)(mutationName)}MutationVariables>, 'mutationFn'>`;
332
+ const optionsParam = t.identifier('options');
333
+ optionsParam.optional = true;
334
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
335
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
336
+ const hookExport = t.exportNamedDeclaration(hookFunc);
337
+ (0, babel_ast_1.addJSDocComment)(hookExport, [
338
+ `Mutation hook for updating a ${typeName}`,
339
+ '',
340
+ '@example',
341
+ '```tsx',
342
+ `const { mutate, isPending } = ${hookName}();`,
343
+ '',
344
+ 'mutate({',
345
+ ' input: {',
346
+ ` ${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},`,
347
+ ' patch: {',
348
+ ' // ... fields to update',
349
+ ' },',
350
+ ' },',
351
+ '});',
352
+ '```',
353
+ ]);
354
+ statements.push(hookExport);
355
+ const code = (0, babel_ast_1.generateCode)(statements);
356
+ const content = (0, utils_1.getGeneratedFileHeader)(`Update mutation hook for ${typeName}`) + '\n\n' + code;
344
357
  return {
345
358
  fileName: (0, utils_1.getUpdateMutationFileName)(table),
346
- content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
359
+ content,
347
360
  };
348
361
  }
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
362
  function generateDeleteMutationHook(table, options = {}) {
357
- const { reactQueryEnabled = true } = options;
358
- // Mutations require React Query - skip generation when disabled
363
+ const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
359
364
  if (!reactQueryEnabled) {
360
365
  return null;
361
366
  }
362
- // Check if delete mutation exists
363
367
  if (table.query?.delete === null) {
364
368
  return null;
365
369
  }
366
- const project = (0, ts_ast_1.createProject)();
367
370
  const { typeName } = (0, utils_1.getTableNames)(table);
368
371
  const hookName = (0, utils_1.getDeleteMutationHookName)(table);
369
372
  const mutationName = (0, utils_1.getDeleteMutationName)(table);
370
- // Get primary key info dynamically from table constraints
373
+ const keysName = `${(0, utils_1.lcFirst)(typeName)}Keys`;
374
+ const mutationKeysName = `${(0, utils_1.lcFirst)(typeName)}MutationKeys`;
375
+ const scopeTypeName = `${typeName}Scope`;
371
376
  const pkFields = (0, utils_1.getPrimaryKeyInfo)(table);
372
- const pkField = pkFields[0]; // Use first PK field
373
- // Generate GraphQL document via AST
377
+ const pkField = pkFields[0];
374
378
  const mutationAST = (0, gql_ast_1.buildDeleteMutationAST)({ table });
375
379
  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
- }),
380
+ const statements = [];
381
+ const reactQueryImport = t.importDeclaration([
382
+ t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
383
+ t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
384
+ ], t.stringLiteral('@tanstack/react-query'));
385
+ statements.push(reactQueryImport);
386
+ const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
387
+ reactQueryTypeImport.importKind = 'type';
388
+ statements.push(reactQueryTypeImport);
389
+ const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
390
+ statements.push(clientImport);
391
+ if (useCentralizedKeys) {
392
+ const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
393
+ statements.push(queryKeyImport);
394
+ if (hasRelationships) {
395
+ const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
396
+ scopeTypeImport.importKind = 'type';
397
+ statements.push(scopeTypeImport);
398
+ }
399
+ const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
400
+ statements.push(mutationKeyImport);
401
+ }
402
+ const mutationDocConst = t.variableDeclaration('const', [
403
+ t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
390
404
  ]);
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
- },
405
+ statements.push(t.exportNamedDeclaration(mutationDocConst));
406
+ const pkTypeAnnotation = pkField.tsType === 'string'
407
+ ? t.tsStringKeyword()
408
+ : pkField.tsType === 'number'
409
+ ? t.tsNumberKeyword()
410
+ : t.tsTypeReference(t.identifier(pkField.tsType));
411
+ const variablesInterfaceBody = t.tsInterfaceBody([
412
+ t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
413
+ t.tsPropertySignature(t.identifier(pkField.name), t.tsTypeAnnotation(pkTypeAnnotation)),
414
+ ]))),
415
+ ]);
416
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
417
+ statements.push(t.exportNamedDeclaration(variablesInterface));
418
+ const deletedPkProp = t.tsPropertySignature(t.identifier(`deleted${(0, utils_1.ucFirst)(pkField.name)}`), t.tsTypeAnnotation(t.tsUnionType([pkTypeAnnotation, t.tsNullKeyword()])));
419
+ const clientMutationIdProp = t.tsPropertySignature(t.identifier('clientMutationId'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()])));
420
+ const resultInterfaceBody = t.tsInterfaceBody([
421
+ t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([clientMutationIdProp, deletedPkProp]))),
422
+ ]);
423
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationResult`), null, null, resultInterfaceBody);
424
+ statements.push(t.exportNamedDeclaration(resultInterface));
425
+ const hookBodyStatements = [];
426
+ hookBodyStatements.push(t.variableDeclaration('const', [
427
+ t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
419
428
  ]));
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
- });
429
+ const mutationOptions = [];
430
+ if (useCentralizedKeys) {
431
+ mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))));
432
+ }
433
+ 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`)))], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${mutationName}MutationDocument`), t.identifier('variables')], [
434
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationResult`)),
435
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`)),
436
+ ]))));
437
+ const detailQueryKey = useCentralizedKeys
438
+ ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name))])
439
+ : t.arrayExpression([
440
+ t.stringLiteral(typeName.toLowerCase()),
441
+ t.stringLiteral('detail'),
442
+ t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name)),
443
+ ]);
444
+ const listQueryKey = useCentralizedKeys
445
+ ? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
446
+ : t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
447
+ mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([t.identifier('_'), t.identifier('variables')], t.blockStatement([
448
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('removeQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), detailQueryKey)])])),
449
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), listQueryKey)])])),
450
+ ]))));
451
+ mutationOptions.push(t.spreadElement(t.identifier('options')));
452
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
453
+ const optionsTypeStr = `Omit<UseMutationOptions<${(0, utils_1.ucFirst)(mutationName)}MutationResult, Error, ${(0, utils_1.ucFirst)(mutationName)}MutationVariables>, 'mutationFn'>`;
454
+ const optionsParam = t.identifier('options');
455
+ optionsParam.optional = true;
456
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
457
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
458
+ const hookExport = t.exportNamedDeclaration(hookFunc);
459
+ (0, babel_ast_1.addJSDocComment)(hookExport, [
460
+ `Mutation hook for deleting a ${typeName}`,
461
+ '',
462
+ '@example',
463
+ '```tsx',
464
+ `const { mutate, isPending } = ${hookName}();`,
465
+ '',
466
+ 'mutate({',
467
+ ' input: {',
468
+ ` ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},`,
469
+ ' },',
470
+ '});',
471
+ '```',
472
+ ]);
473
+ statements.push(hookExport);
474
+ const code = (0, babel_ast_1.generateCode)(statements);
475
+ const content = (0, utils_1.getGeneratedFileHeader)(`Delete mutation hook for ${typeName}`) + '\n\n' + code;
467
476
  return {
468
477
  fileName: (0, utils_1.getDeleteMutationFileName)(table),
469
- content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
478
+ content,
470
479
  };
471
480
  }
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
481
  function generateAllMutationHooks(tables, options = {}) {
480
482
  const files = [];
481
483
  for (const table of tables) {