@constructive-io/graphql-codegen 2.23.3 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -2
- package/cli/codegen/babel-ast.d.ts +46 -0
- package/cli/codegen/babel-ast.js +145 -0
- package/cli/codegen/barrel.d.ts +7 -2
- package/cli/codegen/barrel.js +159 -97
- 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 +246 -335
- package/cli/codegen/index.d.ts +3 -0
- package/cli/codegen/index.js +72 -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 +3 -19
- package/cli/codegen/mutations.js +372 -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 +403 -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 +3 -11
- package/cli/codegen/queries.js +582 -389
- 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/esm/cli/codegen/babel-ast.d.ts +46 -0
- package/esm/cli/codegen/babel-ast.js +97 -0
- package/esm/cli/codegen/barrel.d.ts +7 -2
- package/esm/cli/codegen/barrel.js +126 -97
- 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 +214 -336
- package/esm/cli/codegen/index.d.ts +3 -0
- package/esm/cli/codegen/index.js +68 -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 +3 -19
- package/esm/cli/codegen/mutations.js +340 -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 +371 -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 +3 -11
- package/esm/cli/codegen/queries.js +550 -390
- 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/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,46 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import { generateCode, addJSDocComment, typedParam } 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, } = 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();
|
|
45
31
|
for (const field of scalarFields) {
|
|
46
32
|
const cleanType = field.type.gqlType.replace(/!/g, '');
|
|
@@ -48,161 +34,141 @@ export function generateCreateMutationHook(table, options = {}) {
|
|
|
48
34
|
usedEnums.add(cleanType);
|
|
49
35
|
}
|
|
50
36
|
}
|
|
51
|
-
// Generate GraphQL document via AST
|
|
52
37
|
const mutationAST = buildCreateMutationAST({ table });
|
|
53
38
|
const mutationDocument = printGraphQL(mutationAST);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
createImport({
|
|
69
|
-
moduleSpecifier: '../types',
|
|
70
|
-
typeOnlyNamedImports: [typeName],
|
|
71
|
-
}),
|
|
72
|
-
];
|
|
73
|
-
// Add import for enum types from schema-types if any are used
|
|
39
|
+
const statements = [];
|
|
40
|
+
const reactQueryImport = t.importDeclaration([
|
|
41
|
+
t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
|
|
42
|
+
t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
|
|
43
|
+
], t.stringLiteral('@tanstack/react-query'));
|
|
44
|
+
statements.push(reactQueryImport);
|
|
45
|
+
const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
|
|
46
|
+
reactQueryTypeImport.importKind = 'type';
|
|
47
|
+
statements.push(reactQueryTypeImport);
|
|
48
|
+
const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
|
|
49
|
+
statements.push(clientImport);
|
|
50
|
+
const typesImport = t.importDeclaration([t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
51
|
+
typesImport.importKind = 'type';
|
|
52
|
+
statements.push(typesImport);
|
|
74
53
|
if (usedEnums.size > 0) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
54
|
+
const enumImport = t.importDeclaration(Array.from(usedEnums).sort().map((e) => t.importSpecifier(t.identifier(e), t.identifier(e))), t.stringLiteral('../schema-types'));
|
|
55
|
+
enumImport.importKind = 'type';
|
|
56
|
+
statements.push(enumImport);
|
|
57
|
+
}
|
|
58
|
+
if (useCentralizedKeys) {
|
|
59
|
+
const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
|
|
60
|
+
statements.push(queryKeyImport);
|
|
61
|
+
if (hasRelationships) {
|
|
62
|
+
const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
|
|
63
|
+
scopeTypeImport.importKind = 'type';
|
|
64
|
+
statements.push(scopeTypeImport);
|
|
65
|
+
}
|
|
66
|
+
const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
|
|
67
|
+
statements.push(mutationKeyImport);
|
|
79
68
|
}
|
|
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
|
|
69
|
+
const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
70
|
+
reExportDecl.exportKind = 'type';
|
|
71
|
+
statements.push(reExportDecl);
|
|
72
|
+
const mutationDocConst = t.variableDeclaration('const', [
|
|
73
|
+
t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
|
|
74
|
+
]);
|
|
75
|
+
statements.push(t.exportNamedDeclaration(mutationDocConst));
|
|
96
76
|
const inputFields = scalarFields
|
|
97
77
|
.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
|
-
],
|
|
78
|
+
.map((f) => {
|
|
79
|
+
const prop = t.tsPropertySignature(t.identifier(f.name), t.tsTypeAnnotation(t.tsUnionType([
|
|
80
|
+
t.tsTypeReference(t.identifier(fieldTypeToTs(f.type))),
|
|
81
|
+
t.tsNullKeyword(),
|
|
82
|
+
])));
|
|
83
|
+
prop.optional = true;
|
|
84
|
+
return prop;
|
|
172
85
|
});
|
|
86
|
+
const createInputInterface = t.tsInterfaceDeclaration(t.identifier(`${typeName}CreateInput`), null, null, t.tsInterfaceBody(inputFields));
|
|
87
|
+
addJSDocComment(createInputInterface, [`Input type for creating a ${typeName}`]);
|
|
88
|
+
statements.push(createInputInterface);
|
|
89
|
+
const variablesInterfaceBody = t.tsInterfaceBody([
|
|
90
|
+
t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
|
|
91
|
+
t.tsPropertySignature(t.identifier(lcFirst(typeName)), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${typeName}CreateInput`)))),
|
|
92
|
+
]))),
|
|
93
|
+
]);
|
|
94
|
+
const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
|
|
95
|
+
statements.push(t.exportNamedDeclaration(variablesInterface));
|
|
96
|
+
const resultInterfaceBody = t.tsInterfaceBody([
|
|
97
|
+
t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([
|
|
98
|
+
t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(typeName)))),
|
|
99
|
+
]))),
|
|
100
|
+
]);
|
|
101
|
+
const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationResult`), null, null, resultInterfaceBody);
|
|
102
|
+
statements.push(t.exportNamedDeclaration(resultInterface));
|
|
103
|
+
const hookBodyStatements = [];
|
|
104
|
+
hookBodyStatements.push(t.variableDeclaration('const', [
|
|
105
|
+
t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
|
|
106
|
+
]));
|
|
107
|
+
const mutationOptions = [];
|
|
108
|
+
if (useCentralizedKeys) {
|
|
109
|
+
mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.callExpression(t.memberExpression(t.identifier(mutationKeysName), t.identifier('create')), [])));
|
|
110
|
+
}
|
|
111
|
+
mutationOptions.push(t.objectProperty(t.identifier('mutationFn'), t.arrowFunctionExpression([typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], t.callExpression(t.identifier('execute'), [
|
|
112
|
+
t.identifier(`${mutationName}MutationDocument`),
|
|
113
|
+
t.identifier('variables'),
|
|
114
|
+
]))));
|
|
115
|
+
const invalidateQueryKey = useCentralizedKeys
|
|
116
|
+
? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
|
|
117
|
+
: t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
|
|
118
|
+
mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([], t.blockStatement([
|
|
119
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), invalidateQueryKey)])])),
|
|
120
|
+
]))));
|
|
121
|
+
mutationOptions.push(t.spreadElement(t.identifier('options')));
|
|
122
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
|
|
123
|
+
const optionsTypeStr = `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`;
|
|
124
|
+
const optionsParam = t.identifier('options');
|
|
125
|
+
optionsParam.optional = true;
|
|
126
|
+
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
|
|
127
|
+
const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
|
|
128
|
+
const hookExport = t.exportNamedDeclaration(hookFunc);
|
|
129
|
+
addJSDocComment(hookExport, [
|
|
130
|
+
`Mutation hook for creating a ${typeName}`,
|
|
131
|
+
'',
|
|
132
|
+
'@example',
|
|
133
|
+
'```tsx',
|
|
134
|
+
`const { mutate, isPending } = ${hookName}();`,
|
|
135
|
+
'',
|
|
136
|
+
'mutate({',
|
|
137
|
+
' input: {',
|
|
138
|
+
` ${lcFirst(typeName)}: {`,
|
|
139
|
+
' // ... fields',
|
|
140
|
+
' },',
|
|
141
|
+
' },',
|
|
142
|
+
'});',
|
|
143
|
+
'```',
|
|
144
|
+
]);
|
|
145
|
+
statements.push(hookExport);
|
|
146
|
+
const code = generateCode(statements);
|
|
147
|
+
const content = getGeneratedFileHeader(`Create mutation hook for ${typeName}`) + '\n\n' + code;
|
|
173
148
|
return {
|
|
174
149
|
fileName: getCreateMutationFileName(table),
|
|
175
|
-
content
|
|
150
|
+
content,
|
|
176
151
|
};
|
|
177
152
|
}
|
|
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
153
|
export function generateUpdateMutationHook(table, options = {}) {
|
|
186
|
-
const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
|
|
187
|
-
// Mutations require React Query - skip generation when disabled
|
|
154
|
+
const { reactQueryEnabled = true, enumsFromSchemaTypes = [], useCentralizedKeys = true, hasRelationships = false, } = options;
|
|
188
155
|
if (!reactQueryEnabled) {
|
|
189
156
|
return null;
|
|
190
157
|
}
|
|
191
|
-
// Check if update mutation exists
|
|
192
158
|
if (table.query?.update === null) {
|
|
193
159
|
return null;
|
|
194
160
|
}
|
|
195
161
|
const enumSet = new Set(enumsFromSchemaTypes);
|
|
196
|
-
const project = createProject();
|
|
197
162
|
const { typeName, singularName } = getTableNames(table);
|
|
198
163
|
const hookName = getUpdateMutationHookName(table);
|
|
199
164
|
const mutationName = getUpdateMutationName(table);
|
|
200
165
|
const scalarFields = getScalarFields(table);
|
|
201
|
-
|
|
166
|
+
const keysName = `${lcFirst(typeName)}Keys`;
|
|
167
|
+
const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
|
|
168
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
202
169
|
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
|
|
170
|
+
const pkField = pkFields[0];
|
|
171
|
+
const pkFieldNames = new Set(pkFields.map((pk) => pk.name));
|
|
206
172
|
const usedEnums = new Set();
|
|
207
173
|
for (const field of scalarFields) {
|
|
208
174
|
const cleanType = field.type.gqlType.replace(/!/g, '');
|
|
@@ -210,266 +176,256 @@ export function generateUpdateMutationHook(table, options = {}) {
|
|
|
210
176
|
usedEnums.add(cleanType);
|
|
211
177
|
}
|
|
212
178
|
}
|
|
213
|
-
// Generate GraphQL document via AST
|
|
214
179
|
const mutationAST = buildUpdateMutationAST({ table });
|
|
215
180
|
const mutationDocument = printGraphQL(mutationAST);
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
createImport({
|
|
231
|
-
moduleSpecifier: '../types',
|
|
232
|
-
typeOnlyNamedImports: [typeName],
|
|
233
|
-
}),
|
|
234
|
-
];
|
|
235
|
-
// Add import for enum types from schema-types if any are used
|
|
181
|
+
const statements = [];
|
|
182
|
+
const reactQueryImport = t.importDeclaration([
|
|
183
|
+
t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
|
|
184
|
+
t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
|
|
185
|
+
], t.stringLiteral('@tanstack/react-query'));
|
|
186
|
+
statements.push(reactQueryImport);
|
|
187
|
+
const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
|
|
188
|
+
reactQueryTypeImport.importKind = 'type';
|
|
189
|
+
statements.push(reactQueryTypeImport);
|
|
190
|
+
const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
|
|
191
|
+
statements.push(clientImport);
|
|
192
|
+
const typesImport = t.importDeclaration([t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
193
|
+
typesImport.importKind = 'type';
|
|
194
|
+
statements.push(typesImport);
|
|
236
195
|
if (usedEnums.size > 0) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}));
|
|
196
|
+
const enumImport = t.importDeclaration(Array.from(usedEnums).sort().map((e) => t.importSpecifier(t.identifier(e), t.identifier(e))), t.stringLiteral('../schema-types'));
|
|
197
|
+
enumImport.importKind = 'type';
|
|
198
|
+
statements.push(enumImport);
|
|
241
199
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
200
|
+
if (useCentralizedKeys) {
|
|
201
|
+
const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
|
|
202
|
+
statements.push(queryKeyImport);
|
|
203
|
+
if (hasRelationships) {
|
|
204
|
+
const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
|
|
205
|
+
scopeTypeImport.importKind = 'type';
|
|
206
|
+
statements.push(scopeTypeImport);
|
|
207
|
+
}
|
|
208
|
+
const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
|
|
209
|
+
statements.push(mutationKeyImport);
|
|
210
|
+
}
|
|
211
|
+
const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
212
|
+
reExportDecl.exportKind = 'type';
|
|
213
|
+
statements.push(reExportDecl);
|
|
214
|
+
const mutationDocConst = t.variableDeclaration('const', [
|
|
215
|
+
t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
|
|
216
|
+
]);
|
|
217
|
+
statements.push(t.exportNamedDeclaration(mutationDocConst));
|
|
258
218
|
const patchFields = scalarFields
|
|
259
219
|
.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
|
-
],
|
|
220
|
+
.map((f) => {
|
|
221
|
+
const prop = t.tsPropertySignature(t.identifier(f.name), t.tsTypeAnnotation(t.tsUnionType([
|
|
222
|
+
t.tsTypeReference(t.identifier(fieldTypeToTs(f.type))),
|
|
223
|
+
t.tsNullKeyword(),
|
|
224
|
+
])));
|
|
225
|
+
prop.optional = true;
|
|
226
|
+
return prop;
|
|
337
227
|
});
|
|
228
|
+
const patchInterface = t.tsInterfaceDeclaration(t.identifier(`${typeName}Patch`), null, null, t.tsInterfaceBody(patchFields));
|
|
229
|
+
addJSDocComment(patchInterface, [`Patch type for updating a ${typeName} - all fields optional`]);
|
|
230
|
+
statements.push(patchInterface);
|
|
231
|
+
const pkTypeAnnotation = pkField.tsType === 'string'
|
|
232
|
+
? t.tsStringKeyword()
|
|
233
|
+
: pkField.tsType === 'number'
|
|
234
|
+
? t.tsNumberKeyword()
|
|
235
|
+
: t.tsTypeReference(t.identifier(pkField.tsType));
|
|
236
|
+
const variablesInterfaceBody = t.tsInterfaceBody([
|
|
237
|
+
t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
|
|
238
|
+
t.tsPropertySignature(t.identifier(pkField.name), t.tsTypeAnnotation(pkTypeAnnotation)),
|
|
239
|
+
t.tsPropertySignature(t.identifier('patch'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${typeName}Patch`)))),
|
|
240
|
+
]))),
|
|
241
|
+
]);
|
|
242
|
+
const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
|
|
243
|
+
statements.push(t.exportNamedDeclaration(variablesInterface));
|
|
244
|
+
const resultInterfaceBody = t.tsInterfaceBody([
|
|
245
|
+
t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([
|
|
246
|
+
t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(typeName)))),
|
|
247
|
+
]))),
|
|
248
|
+
]);
|
|
249
|
+
const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationResult`), null, null, resultInterfaceBody);
|
|
250
|
+
statements.push(t.exportNamedDeclaration(resultInterface));
|
|
251
|
+
const hookBodyStatements = [];
|
|
252
|
+
hookBodyStatements.push(t.variableDeclaration('const', [
|
|
253
|
+
t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
|
|
254
|
+
]));
|
|
255
|
+
const mutationOptions = [];
|
|
256
|
+
if (useCentralizedKeys) {
|
|
257
|
+
mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))));
|
|
258
|
+
}
|
|
259
|
+
mutationOptions.push(t.objectProperty(t.identifier('mutationFn'), t.arrowFunctionExpression([typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], t.callExpression(t.identifier('execute'), [
|
|
260
|
+
t.identifier(`${mutationName}MutationDocument`),
|
|
261
|
+
t.identifier('variables'),
|
|
262
|
+
]))));
|
|
263
|
+
const detailQueryKey = useCentralizedKeys
|
|
264
|
+
? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name))])
|
|
265
|
+
: t.arrayExpression([
|
|
266
|
+
t.stringLiteral(typeName.toLowerCase()),
|
|
267
|
+
t.stringLiteral('detail'),
|
|
268
|
+
t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name)),
|
|
269
|
+
]);
|
|
270
|
+
const listQueryKey = useCentralizedKeys
|
|
271
|
+
? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
|
|
272
|
+
: t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
|
|
273
|
+
mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([t.identifier('_'), t.identifier('variables')], t.blockStatement([
|
|
274
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), detailQueryKey)])])),
|
|
275
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), listQueryKey)])])),
|
|
276
|
+
]))));
|
|
277
|
+
mutationOptions.push(t.spreadElement(t.identifier('options')));
|
|
278
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
|
|
279
|
+
const optionsTypeStr = `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`;
|
|
280
|
+
const optionsParam = t.identifier('options');
|
|
281
|
+
optionsParam.optional = true;
|
|
282
|
+
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
|
|
283
|
+
const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
|
|
284
|
+
const hookExport = t.exportNamedDeclaration(hookFunc);
|
|
285
|
+
addJSDocComment(hookExport, [
|
|
286
|
+
`Mutation hook for updating a ${typeName}`,
|
|
287
|
+
'',
|
|
288
|
+
'@example',
|
|
289
|
+
'```tsx',
|
|
290
|
+
`const { mutate, isPending } = ${hookName}();`,
|
|
291
|
+
'',
|
|
292
|
+
'mutate({',
|
|
293
|
+
' input: {',
|
|
294
|
+
` ${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},`,
|
|
295
|
+
' patch: {',
|
|
296
|
+
' // ... fields to update',
|
|
297
|
+
' },',
|
|
298
|
+
' },',
|
|
299
|
+
'});',
|
|
300
|
+
'```',
|
|
301
|
+
]);
|
|
302
|
+
statements.push(hookExport);
|
|
303
|
+
const code = generateCode(statements);
|
|
304
|
+
const content = getGeneratedFileHeader(`Update mutation hook for ${typeName}`) + '\n\n' + code;
|
|
338
305
|
return {
|
|
339
306
|
fileName: getUpdateMutationFileName(table),
|
|
340
|
-
content
|
|
307
|
+
content,
|
|
341
308
|
};
|
|
342
309
|
}
|
|
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
310
|
export function generateDeleteMutationHook(table, options = {}) {
|
|
351
|
-
const { reactQueryEnabled = true } = options;
|
|
352
|
-
// Mutations require React Query - skip generation when disabled
|
|
311
|
+
const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
|
|
353
312
|
if (!reactQueryEnabled) {
|
|
354
313
|
return null;
|
|
355
314
|
}
|
|
356
|
-
// Check if delete mutation exists
|
|
357
315
|
if (table.query?.delete === null) {
|
|
358
316
|
return null;
|
|
359
317
|
}
|
|
360
|
-
const project = createProject();
|
|
361
318
|
const { typeName } = getTableNames(table);
|
|
362
319
|
const hookName = getDeleteMutationHookName(table);
|
|
363
320
|
const mutationName = getDeleteMutationName(table);
|
|
364
|
-
|
|
321
|
+
const keysName = `${lcFirst(typeName)}Keys`;
|
|
322
|
+
const mutationKeysName = `${lcFirst(typeName)}MutationKeys`;
|
|
323
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
365
324
|
const pkFields = getPrimaryKeyInfo(table);
|
|
366
|
-
const pkField = pkFields[0];
|
|
367
|
-
// Generate GraphQL document via AST
|
|
325
|
+
const pkField = pkFields[0];
|
|
368
326
|
const mutationAST = buildDeleteMutationAST({ table });
|
|
369
327
|
const mutationDocument = printGraphQL(mutationAST);
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
328
|
+
const statements = [];
|
|
329
|
+
const reactQueryImport = t.importDeclaration([
|
|
330
|
+
t.importSpecifier(t.identifier('useMutation'), t.identifier('useMutation')),
|
|
331
|
+
t.importSpecifier(t.identifier('useQueryClient'), t.identifier('useQueryClient')),
|
|
332
|
+
], t.stringLiteral('@tanstack/react-query'));
|
|
333
|
+
statements.push(reactQueryImport);
|
|
334
|
+
const reactQueryTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('UseMutationOptions'), t.identifier('UseMutationOptions'))], t.stringLiteral('@tanstack/react-query'));
|
|
335
|
+
reactQueryTypeImport.importKind = 'type';
|
|
336
|
+
statements.push(reactQueryTypeImport);
|
|
337
|
+
const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
|
|
338
|
+
statements.push(clientImport);
|
|
339
|
+
if (useCentralizedKeys) {
|
|
340
|
+
const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
|
|
341
|
+
statements.push(queryKeyImport);
|
|
342
|
+
if (hasRelationships) {
|
|
343
|
+
const scopeTypeImport = t.importDeclaration([t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName))], t.stringLiteral('../query-keys'));
|
|
344
|
+
scopeTypeImport.importKind = 'type';
|
|
345
|
+
statements.push(scopeTypeImport);
|
|
346
|
+
}
|
|
347
|
+
const mutationKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(mutationKeysName), t.identifier(mutationKeysName))], t.stringLiteral('../mutation-keys'));
|
|
348
|
+
statements.push(mutationKeyImport);
|
|
349
|
+
}
|
|
350
|
+
const mutationDocConst = t.variableDeclaration('const', [
|
|
351
|
+
t.variableDeclarator(t.identifier(`${mutationName}MutationDocument`), t.templateLiteral([t.templateElement({ raw: '\n' + mutationDocument, cooked: '\n' + mutationDocument }, true)], [])),
|
|
384
352
|
]);
|
|
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
|
-
},
|
|
353
|
+
statements.push(t.exportNamedDeclaration(mutationDocConst));
|
|
354
|
+
const pkTypeAnnotation = pkField.tsType === 'string'
|
|
355
|
+
? t.tsStringKeyword()
|
|
356
|
+
: pkField.tsType === 'number'
|
|
357
|
+
? t.tsNumberKeyword()
|
|
358
|
+
: t.tsTypeReference(t.identifier(pkField.tsType));
|
|
359
|
+
const variablesInterfaceBody = t.tsInterfaceBody([
|
|
360
|
+
t.tsPropertySignature(t.identifier('input'), t.tsTypeAnnotation(t.tsTypeLiteral([
|
|
361
|
+
t.tsPropertySignature(t.identifier(pkField.name), t.tsTypeAnnotation(pkTypeAnnotation)),
|
|
362
|
+
]))),
|
|
363
|
+
]);
|
|
364
|
+
const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationVariables`), null, null, variablesInterfaceBody);
|
|
365
|
+
statements.push(t.exportNamedDeclaration(variablesInterface));
|
|
366
|
+
const deletedPkProp = t.tsPropertySignature(t.identifier(`deleted${ucFirst(pkField.name)}`), t.tsTypeAnnotation(t.tsUnionType([pkTypeAnnotation, t.tsNullKeyword()])));
|
|
367
|
+
const clientMutationIdProp = t.tsPropertySignature(t.identifier('clientMutationId'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()])));
|
|
368
|
+
const resultInterfaceBody = t.tsInterfaceBody([
|
|
369
|
+
t.tsPropertySignature(t.identifier(mutationName), t.tsTypeAnnotation(t.tsTypeLiteral([clientMutationIdProp, deletedPkProp]))),
|
|
370
|
+
]);
|
|
371
|
+
const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(mutationName)}MutationResult`), null, null, resultInterfaceBody);
|
|
372
|
+
statements.push(t.exportNamedDeclaration(resultInterface));
|
|
373
|
+
const hookBodyStatements = [];
|
|
374
|
+
hookBodyStatements.push(t.variableDeclaration('const', [
|
|
375
|
+
t.variableDeclarator(t.identifier('queryClient'), t.callExpression(t.identifier('useQueryClient'), [])),
|
|
413
376
|
]));
|
|
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
|
-
});
|
|
377
|
+
const mutationOptions = [];
|
|
378
|
+
if (useCentralizedKeys) {
|
|
379
|
+
mutationOptions.push(t.objectProperty(t.identifier('mutationKey'), t.memberExpression(t.identifier(mutationKeysName), t.identifier('all'))));
|
|
380
|
+
}
|
|
381
|
+
mutationOptions.push(t.objectProperty(t.identifier('mutationFn'), t.arrowFunctionExpression([typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(mutationName)}MutationVariables`)))], t.callExpression(t.identifier('execute'), [
|
|
382
|
+
t.identifier(`${mutationName}MutationDocument`),
|
|
383
|
+
t.identifier('variables'),
|
|
384
|
+
]))));
|
|
385
|
+
const detailQueryKey = useCentralizedKeys
|
|
386
|
+
? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name))])
|
|
387
|
+
: t.arrayExpression([
|
|
388
|
+
t.stringLiteral(typeName.toLowerCase()),
|
|
389
|
+
t.stringLiteral('detail'),
|
|
390
|
+
t.memberExpression(t.memberExpression(t.identifier('variables'), t.identifier('input')), t.identifier(pkField.name)),
|
|
391
|
+
]);
|
|
392
|
+
const listQueryKey = useCentralizedKeys
|
|
393
|
+
? t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])
|
|
394
|
+
: t.arrayExpression([t.stringLiteral(typeName.toLowerCase()), t.stringLiteral('list')]);
|
|
395
|
+
mutationOptions.push(t.objectProperty(t.identifier('onSuccess'), t.arrowFunctionExpression([t.identifier('_'), t.identifier('variables')], t.blockStatement([
|
|
396
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('removeQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), detailQueryKey)])])),
|
|
397
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('invalidateQueries')), [t.objectExpression([t.objectProperty(t.identifier('queryKey'), listQueryKey)])])),
|
|
398
|
+
]))));
|
|
399
|
+
mutationOptions.push(t.spreadElement(t.identifier('options')));
|
|
400
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useMutation'), [t.objectExpression(mutationOptions)])));
|
|
401
|
+
const optionsTypeStr = `Omit<UseMutationOptions<${ucFirst(mutationName)}MutationResult, Error, ${ucFirst(mutationName)}MutationVariables>, 'mutationFn'>`;
|
|
402
|
+
const optionsParam = t.identifier('options');
|
|
403
|
+
optionsParam.optional = true;
|
|
404
|
+
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
|
|
405
|
+
const hookFunc = t.functionDeclaration(t.identifier(hookName), [optionsParam], t.blockStatement(hookBodyStatements));
|
|
406
|
+
const hookExport = t.exportNamedDeclaration(hookFunc);
|
|
407
|
+
addJSDocComment(hookExport, [
|
|
408
|
+
`Mutation hook for deleting a ${typeName}`,
|
|
409
|
+
'',
|
|
410
|
+
'@example',
|
|
411
|
+
'```tsx',
|
|
412
|
+
`const { mutate, isPending } = ${hookName}();`,
|
|
413
|
+
'',
|
|
414
|
+
'mutate({',
|
|
415
|
+
' input: {',
|
|
416
|
+
` ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},`,
|
|
417
|
+
' },',
|
|
418
|
+
'});',
|
|
419
|
+
'```',
|
|
420
|
+
]);
|
|
421
|
+
statements.push(hookExport);
|
|
422
|
+
const code = generateCode(statements);
|
|
423
|
+
const content = getGeneratedFileHeader(`Delete mutation hook for ${typeName}`) + '\n\n' + code;
|
|
461
424
|
return {
|
|
462
425
|
fileName: getDeleteMutationFileName(table),
|
|
463
|
-
content
|
|
426
|
+
content,
|
|
464
427
|
};
|
|
465
428
|
}
|
|
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
429
|
export function generateAllMutationHooks(tables, options = {}) {
|
|
474
430
|
const files = [];
|
|
475
431
|
for (const table of tables) {
|