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