@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,31 +1,46 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import { generateCode, addJSDocComment, typedParam } from './babel-ast';
|
|
2
3
|
import { buildListQueryAST, buildSingleQueryAST, printGraphQL, } from './gql-ast';
|
|
3
|
-
import { getTableNames, getListQueryHookName, getSingleQueryHookName, getListQueryFileName, getSingleQueryFileName, getAllRowsQueryName, getSingleRowQueryName, getFilterTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, getPrimaryKeyInfo, toScreamingSnake, ucFirst, } from './utils';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { getTableNames, getListQueryHookName, getSingleQueryHookName, getListQueryFileName, getSingleQueryFileName, getAllRowsQueryName, getSingleRowQueryName, getFilterTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, getPrimaryKeyInfo, toScreamingSnake, ucFirst, lcFirst, getGeneratedFileHeader, } from './utils';
|
|
5
|
+
function createUnionType(values) {
|
|
6
|
+
return t.tsUnionType(values.map((v) => t.tsLiteralType(t.stringLiteral(v))));
|
|
7
|
+
}
|
|
8
|
+
function createFilterInterfaceDeclaration(name, fieldFilters, isExported = true) {
|
|
9
|
+
const properties = [];
|
|
10
|
+
for (const filter of fieldFilters) {
|
|
11
|
+
const prop = t.tsPropertySignature(t.identifier(filter.fieldName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filter.filterType))));
|
|
12
|
+
prop.optional = true;
|
|
13
|
+
properties.push(prop);
|
|
14
|
+
}
|
|
15
|
+
const andProp = t.tsPropertySignature(t.identifier('and'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(name)))));
|
|
16
|
+
andProp.optional = true;
|
|
17
|
+
properties.push(andProp);
|
|
18
|
+
const orProp = t.tsPropertySignature(t.identifier('or'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(name)))));
|
|
19
|
+
orProp.optional = true;
|
|
20
|
+
properties.push(orProp);
|
|
21
|
+
const notProp = t.tsPropertySignature(t.identifier('not'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(name))));
|
|
22
|
+
notProp.optional = true;
|
|
23
|
+
properties.push(notProp);
|
|
24
|
+
const body = t.tsInterfaceBody(properties);
|
|
25
|
+
const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(name), null, null, body);
|
|
26
|
+
if (isExported) {
|
|
27
|
+
return t.exportNamedDeclaration(interfaceDecl);
|
|
28
|
+
}
|
|
29
|
+
return interfaceDecl;
|
|
30
|
+
}
|
|
10
31
|
export function generateListQueryHook(table, options = {}) {
|
|
11
|
-
const { reactQueryEnabled = true } = options;
|
|
12
|
-
const project = createProject();
|
|
32
|
+
const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
|
|
13
33
|
const { typeName, pluralName } = getTableNames(table);
|
|
14
34
|
const hookName = getListQueryHookName(table);
|
|
15
35
|
const queryName = getAllRowsQueryName(table);
|
|
16
36
|
const filterTypeName = getFilterTypeName(table);
|
|
17
37
|
const orderByTypeName = getOrderByTypeName(table);
|
|
18
38
|
const scalarFields = getScalarFields(table);
|
|
19
|
-
|
|
39
|
+
const keysName = `${lcFirst(typeName)}Keys`;
|
|
40
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
20
41
|
const queryAST = buildListQueryAST({ table });
|
|
21
42
|
const queryDocument = printGraphQL(queryAST);
|
|
22
|
-
const
|
|
23
|
-
// Add file header as leading comment
|
|
24
|
-
const headerText = reactQueryEnabled
|
|
25
|
-
? `List query hook for ${typeName}`
|
|
26
|
-
: `List query functions for ${typeName}`;
|
|
27
|
-
sourceFile.insertText(0, createFileHeader(headerText) + '\n\n');
|
|
28
|
-
// Collect all filter types used by this table's fields
|
|
43
|
+
const statements = [];
|
|
29
44
|
const filterTypesUsed = new Set();
|
|
30
45
|
for (const field of scalarFields) {
|
|
31
46
|
const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
|
|
@@ -33,47 +48,56 @@ export function generateListQueryHook(table, options = {}) {
|
|
|
33
48
|
filterTypesUsed.add(filterType);
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
|
-
// Add imports - conditionally include React Query imports
|
|
37
|
-
const imports = [];
|
|
38
51
|
if (reactQueryEnabled) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
|
|
53
|
+
statements.push(reactQueryImport);
|
|
54
|
+
const reactQueryTypeImport = t.importDeclaration([
|
|
55
|
+
t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
|
|
56
|
+
t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
|
|
57
|
+
], t.stringLiteral('@tanstack/react-query'));
|
|
58
|
+
reactQueryTypeImport.importKind = 'type';
|
|
59
|
+
statements.push(reactQueryTypeImport);
|
|
60
|
+
}
|
|
61
|
+
const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
|
|
62
|
+
statements.push(clientImport);
|
|
63
|
+
const clientTypeImport = t.importDeclaration([
|
|
64
|
+
t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
|
|
65
|
+
], t.stringLiteral('../client'));
|
|
66
|
+
clientTypeImport.importKind = 'type';
|
|
67
|
+
statements.push(clientTypeImport);
|
|
68
|
+
const typesImport = t.importDeclaration([
|
|
69
|
+
t.importSpecifier(t.identifier(typeName), t.identifier(typeName)),
|
|
70
|
+
...Array.from(filterTypesUsed).map((ft) => t.importSpecifier(t.identifier(ft), t.identifier(ft))),
|
|
71
|
+
], t.stringLiteral('../types'));
|
|
72
|
+
typesImport.importKind = 'type';
|
|
73
|
+
statements.push(typesImport);
|
|
74
|
+
if (useCentralizedKeys) {
|
|
75
|
+
const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
|
|
76
|
+
statements.push(queryKeyImport);
|
|
77
|
+
if (hasRelationships) {
|
|
78
|
+
const scopeTypeImport = t.importDeclaration([
|
|
79
|
+
t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
|
|
80
|
+
], t.stringLiteral('../query-keys'));
|
|
81
|
+
scopeTypeImport.importKind = 'type';
|
|
82
|
+
statements.push(scopeTypeImport);
|
|
83
|
+
}
|
|
44
84
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// Re-export entity type
|
|
55
|
-
sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
|
|
56
|
-
// Add section comment
|
|
57
|
-
sourceFile.addStatements('\n// ============================================================================');
|
|
58
|
-
sourceFile.addStatements('// GraphQL Document');
|
|
59
|
-
sourceFile.addStatements('// ============================================================================\n');
|
|
60
|
-
// Add query document constant
|
|
61
|
-
sourceFile.addVariableStatement(createConst(`${queryName}QueryDocument`, '`\n' + queryDocument + '`'));
|
|
62
|
-
// Add section comment
|
|
63
|
-
sourceFile.addStatements('\n// ============================================================================');
|
|
64
|
-
sourceFile.addStatements('// Types');
|
|
65
|
-
sourceFile.addStatements('// ============================================================================\n');
|
|
66
|
-
// Generate filter interface
|
|
85
|
+
const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
86
|
+
reExportDecl.exportKind = 'type';
|
|
87
|
+
statements.push(reExportDecl);
|
|
88
|
+
const queryDocConst = t.variableDeclaration('const', [
|
|
89
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
|
|
90
|
+
t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
|
|
91
|
+
], [])),
|
|
92
|
+
]);
|
|
93
|
+
statements.push(t.exportNamedDeclaration(queryDocConst));
|
|
67
94
|
const fieldFilters = scalarFields
|
|
68
95
|
.map((field) => {
|
|
69
96
|
const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
|
|
70
97
|
return filterType ? { fieldName: field.name, filterType } : null;
|
|
71
98
|
})
|
|
72
99
|
.filter((f) => f !== null);
|
|
73
|
-
|
|
74
|
-
sourceFile.addInterface(createFilterInterface(filterTypeName, fieldFilters, { isExported: false }));
|
|
75
|
-
// Generate OrderBy type
|
|
76
|
-
// Note: Not exported to avoid conflicts with schema-types
|
|
100
|
+
statements.push(createFilterInterfaceDeclaration(filterTypeName, fieldFilters, false));
|
|
77
101
|
const orderByValues = [
|
|
78
102
|
...scalarFields.flatMap((f) => [
|
|
79
103
|
`${toScreamingSnake(f.name)}_ASC`,
|
|
@@ -83,373 +107,509 @@ export function generateListQueryHook(table, options = {}) {
|
|
|
83
107
|
'PRIMARY_KEY_ASC',
|
|
84
108
|
'PRIMARY_KEY_DESC',
|
|
85
109
|
];
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
110
|
+
const orderByTypeAlias = t.tsTypeAliasDeclaration(t.identifier(orderByTypeName), null, createUnionType(orderByValues));
|
|
111
|
+
statements.push(orderByTypeAlias);
|
|
112
|
+
const variablesInterfaceBody = t.tsInterfaceBody([
|
|
113
|
+
(() => {
|
|
114
|
+
const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
|
|
115
|
+
p.optional = true;
|
|
116
|
+
return p;
|
|
117
|
+
})(),
|
|
118
|
+
(() => {
|
|
119
|
+
const p = t.tsPropertySignature(t.identifier('offset'), t.tsTypeAnnotation(t.tsNumberKeyword()));
|
|
120
|
+
p.optional = true;
|
|
121
|
+
return p;
|
|
122
|
+
})(),
|
|
123
|
+
(() => {
|
|
124
|
+
const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterTypeName))));
|
|
125
|
+
p.optional = true;
|
|
126
|
+
return p;
|
|
127
|
+
})(),
|
|
128
|
+
(() => {
|
|
129
|
+
const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByTypeName)))));
|
|
130
|
+
p.optional = true;
|
|
131
|
+
return p;
|
|
132
|
+
})(),
|
|
133
|
+
]);
|
|
134
|
+
const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(pluralName)}QueryVariables`), null, null, variablesInterfaceBody);
|
|
135
|
+
statements.push(t.exportNamedDeclaration(variablesInterface));
|
|
136
|
+
const pageInfoType = t.tsTypeLiteral([
|
|
137
|
+
t.tsPropertySignature(t.identifier('hasNextPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
|
|
138
|
+
t.tsPropertySignature(t.identifier('hasPreviousPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
|
|
139
|
+
t.tsPropertySignature(t.identifier('startCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
|
|
140
|
+
t.tsPropertySignature(t.identifier('endCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
|
|
141
|
+
]);
|
|
142
|
+
const resultType = t.tsTypeLiteral([
|
|
143
|
+
t.tsPropertySignature(t.identifier('totalCount'), t.tsTypeAnnotation(t.tsNumberKeyword())),
|
|
144
|
+
t.tsPropertySignature(t.identifier('nodes'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(typeName))))),
|
|
145
|
+
t.tsPropertySignature(t.identifier('pageInfo'), t.tsTypeAnnotation(pageInfoType)),
|
|
146
|
+
]);
|
|
147
|
+
const resultInterfaceBody = t.tsInterfaceBody([
|
|
148
|
+
t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(resultType)),
|
|
149
|
+
]);
|
|
150
|
+
const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(pluralName)}QueryResult`), null, null, resultInterfaceBody);
|
|
151
|
+
statements.push(t.exportNamedDeclaration(resultInterface));
|
|
152
|
+
if (useCentralizedKeys) {
|
|
153
|
+
const queryKeyConst = t.variableDeclaration('const', [
|
|
154
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('list'))),
|
|
155
|
+
]);
|
|
156
|
+
const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
|
|
157
|
+
addJSDocComment(queryKeyExport, [
|
|
158
|
+
'Query key factory - re-exported from query-keys.ts',
|
|
159
|
+
]);
|
|
160
|
+
statements.push(queryKeyExport);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const queryKeyArrow = t.arrowFunctionExpression([
|
|
164
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
|
|
165
|
+
], t.tsAsExpression(t.arrayExpression([
|
|
166
|
+
t.stringLiteral(typeName.toLowerCase()),
|
|
167
|
+
t.stringLiteral('list'),
|
|
168
|
+
t.identifier('variables'),
|
|
169
|
+
]), t.tsTypeReference(t.identifier('const'))));
|
|
170
|
+
const queryKeyConst = t.variableDeclaration('const', [
|
|
171
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
|
|
172
|
+
]);
|
|
173
|
+
statements.push(t.exportNamedDeclaration(queryKeyConst));
|
|
174
|
+
}
|
|
120
175
|
if (reactQueryEnabled) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
176
|
+
const hookBodyStatements = [];
|
|
177
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
178
|
+
hookBodyStatements.push(t.variableDeclaration('const', [
|
|
179
|
+
t.variableDeclarator(t.objectPattern([
|
|
180
|
+
t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
|
|
181
|
+
t.restElement(t.identifier('queryOptions')),
|
|
182
|
+
]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
|
|
183
|
+
]));
|
|
184
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
185
|
+
t.objectExpression([
|
|
186
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')])),
|
|
187
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
|
|
188
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
189
|
+
t.identifier('variables'),
|
|
190
|
+
]))),
|
|
191
|
+
t.spreadElement(t.identifier('queryOptions')),
|
|
192
|
+
]),
|
|
193
|
+
])));
|
|
194
|
+
}
|
|
195
|
+
else if (useCentralizedKeys) {
|
|
196
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
197
|
+
t.objectExpression([
|
|
198
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')])),
|
|
199
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
|
|
200
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
201
|
+
t.identifier('variables'),
|
|
202
|
+
]))),
|
|
203
|
+
t.spreadElement(t.identifier('options')),
|
|
204
|
+
]),
|
|
205
|
+
])));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
209
|
+
t.objectExpression([
|
|
210
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
|
|
211
|
+
t.identifier('variables'),
|
|
212
|
+
])),
|
|
213
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
|
|
214
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
215
|
+
t.identifier('variables'),
|
|
216
|
+
]))),
|
|
217
|
+
t.spreadElement(t.identifier('options')),
|
|
218
|
+
]),
|
|
219
|
+
])));
|
|
220
|
+
}
|
|
221
|
+
const hookParams = [
|
|
222
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
|
|
223
|
+
];
|
|
224
|
+
let optionsTypeStr;
|
|
225
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
226
|
+
optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
|
|
230
|
+
}
|
|
231
|
+
const optionsParam = t.identifier('options');
|
|
232
|
+
optionsParam.optional = true;
|
|
233
|
+
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
|
|
234
|
+
hookParams.push(optionsParam);
|
|
235
|
+
const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
|
|
236
|
+
const hookExport = t.exportNamedDeclaration(hookFunc);
|
|
237
|
+
const docLines = [
|
|
238
|
+
`Query hook for fetching ${typeName} list`,
|
|
239
|
+
'',
|
|
240
|
+
'@example',
|
|
241
|
+
'```tsx',
|
|
242
|
+
`const { data, isLoading } = ${hookName}({`,
|
|
243
|
+
' first: 10,',
|
|
244
|
+
' filter: { name: { equalTo: "example" } },',
|
|
245
|
+
" orderBy: ['CREATED_AT_DESC'],",
|
|
246
|
+
'});',
|
|
247
|
+
'```',
|
|
248
|
+
];
|
|
249
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
250
|
+
docLines.push('');
|
|
251
|
+
docLines.push('@example With scope for hierarchical cache invalidation');
|
|
252
|
+
docLines.push('```tsx');
|
|
253
|
+
docLines.push(`const { data } = ${hookName}(`);
|
|
254
|
+
docLines.push(' { first: 10 },');
|
|
255
|
+
docLines.push(" { scope: { parentId: 'parent-id' } }");
|
|
256
|
+
docLines.push(');');
|
|
257
|
+
docLines.push('```');
|
|
258
|
+
}
|
|
259
|
+
addJSDocComment(hookExport, docLines);
|
|
260
|
+
statements.push(hookExport);
|
|
163
261
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
\`\`\`ts
|
|
197
|
-
// Direct fetch
|
|
198
|
-
const data = await fetch${ucFirst(pluralName)}Query({ first: 10 });
|
|
199
|
-
|
|
200
|
-
// With QueryClient
|
|
201
|
-
const data = await queryClient.fetchQuery({
|
|
202
|
-
queryKey: ${queryName}QueryKey(variables),
|
|
203
|
-
queryFn: () => fetch${ucFirst(pluralName)}Query(variables),
|
|
204
|
-
});
|
|
205
|
-
\`\`\``,
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
});
|
|
209
|
-
// Prefetch function (for SSR/QueryClient) - only if React Query is enabled
|
|
262
|
+
const fetchFuncBody = t.blockStatement([
|
|
263
|
+
t.returnStatement(t.callExpression(t.identifier('execute'), [
|
|
264
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
265
|
+
t.identifier('variables'),
|
|
266
|
+
t.identifier('options'),
|
|
267
|
+
])),
|
|
268
|
+
]);
|
|
269
|
+
const fetchFunc = t.functionDeclaration(t.identifier(`fetch${ucFirst(pluralName)}Query`), [
|
|
270
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
|
|
271
|
+
typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
|
|
272
|
+
], fetchFuncBody);
|
|
273
|
+
fetchFunc.async = true;
|
|
274
|
+
fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
|
|
275
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
|
|
276
|
+
])));
|
|
277
|
+
const fetchExport = t.exportNamedDeclaration(fetchFunc);
|
|
278
|
+
addJSDocComment(fetchExport, [
|
|
279
|
+
`Fetch ${typeName} list without React hooks`,
|
|
280
|
+
'',
|
|
281
|
+
'@example',
|
|
282
|
+
'```ts',
|
|
283
|
+
'// Direct fetch',
|
|
284
|
+
`const data = await fetch${ucFirst(pluralName)}Query({ first: 10 });`,
|
|
285
|
+
'',
|
|
286
|
+
'// With QueryClient',
|
|
287
|
+
'const data = await queryClient.fetchQuery({',
|
|
288
|
+
` queryKey: ${queryName}QueryKey(variables),`,
|
|
289
|
+
` queryFn: () => fetch${ucFirst(pluralName)}Query(variables),`,
|
|
290
|
+
'});',
|
|
291
|
+
'```',
|
|
292
|
+
]);
|
|
293
|
+
statements.push(fetchExport);
|
|
210
294
|
if (reactQueryEnabled) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
295
|
+
const prefetchParams = [
|
|
296
|
+
typedParam('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
|
|
297
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
|
|
298
|
+
];
|
|
299
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
300
|
+
prefetchParams.push(typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
|
|
301
|
+
}
|
|
302
|
+
prefetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
|
|
303
|
+
let prefetchQueryKeyExpr;
|
|
304
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
305
|
+
prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')]);
|
|
306
|
+
}
|
|
307
|
+
else if (useCentralizedKeys) {
|
|
308
|
+
prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')]);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.identifier('variables')]);
|
|
312
|
+
}
|
|
313
|
+
const prefetchFuncBody = t.blockStatement([
|
|
314
|
+
t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
|
|
315
|
+
t.objectExpression([
|
|
316
|
+
t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
|
|
317
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
|
|
318
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
319
|
+
t.identifier('variables'),
|
|
320
|
+
t.identifier('options'),
|
|
321
|
+
]))),
|
|
322
|
+
]),
|
|
323
|
+
]))),
|
|
324
|
+
]);
|
|
325
|
+
const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${ucFirst(pluralName)}Query`), prefetchParams, prefetchFuncBody);
|
|
326
|
+
prefetchFunc.async = true;
|
|
327
|
+
prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
|
|
328
|
+
const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
|
|
329
|
+
addJSDocComment(prefetchExport, [
|
|
330
|
+
`Prefetch ${typeName} list for SSR or cache warming`,
|
|
331
|
+
'',
|
|
332
|
+
'@example',
|
|
333
|
+
'```ts',
|
|
334
|
+
`await prefetch${ucFirst(pluralName)}Query(queryClient, { first: 10 });`,
|
|
335
|
+
'```',
|
|
336
|
+
]);
|
|
337
|
+
statements.push(prefetchExport);
|
|
251
338
|
}
|
|
339
|
+
const code = generateCode(statements);
|
|
340
|
+
const headerText = reactQueryEnabled
|
|
341
|
+
? `List query hook for ${typeName}`
|
|
342
|
+
: `List query functions for ${typeName}`;
|
|
343
|
+
const content = getGeneratedFileHeader(headerText) + '\n\n' + code;
|
|
252
344
|
return {
|
|
253
345
|
fileName: getListQueryFileName(table),
|
|
254
|
-
content
|
|
346
|
+
content,
|
|
255
347
|
};
|
|
256
348
|
}
|
|
257
|
-
// ============================================================================
|
|
258
|
-
// Single item query hook generator
|
|
259
|
-
// ============================================================================
|
|
260
|
-
/**
|
|
261
|
-
* Generate single item query hook file content using AST
|
|
262
|
-
*/
|
|
263
349
|
export function generateSingleQueryHook(table, options = {}) {
|
|
264
|
-
const { reactQueryEnabled = true } = options;
|
|
265
|
-
const project = createProject();
|
|
350
|
+
const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
|
|
266
351
|
const { typeName, singularName } = getTableNames(table);
|
|
267
352
|
const hookName = getSingleQueryHookName(table);
|
|
268
353
|
const queryName = getSingleRowQueryName(table);
|
|
269
|
-
|
|
354
|
+
const keysName = `${lcFirst(typeName)}Keys`;
|
|
355
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
270
356
|
const pkFields = getPrimaryKeyInfo(table);
|
|
271
|
-
// For simplicity, use first PK field (most common case)
|
|
272
|
-
// Composite PKs would need more complex handling
|
|
273
357
|
const pkField = pkFields[0];
|
|
274
358
|
const pkName = pkField.name;
|
|
275
359
|
const pkTsType = pkField.tsType;
|
|
276
|
-
// Generate GraphQL document via AST
|
|
277
360
|
const queryAST = buildSingleQueryAST({ table });
|
|
278
361
|
const queryDocument = printGraphQL(queryAST);
|
|
279
|
-
const
|
|
280
|
-
// Add file header
|
|
281
|
-
const headerText = reactQueryEnabled
|
|
282
|
-
? `Single item query hook for ${typeName}`
|
|
283
|
-
: `Single item query functions for ${typeName}`;
|
|
284
|
-
sourceFile.insertText(0, createFileHeader(headerText) + '\n\n');
|
|
285
|
-
// Add imports - conditionally include React Query imports
|
|
286
|
-
const imports = [];
|
|
362
|
+
const statements = [];
|
|
287
363
|
if (reactQueryEnabled) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
364
|
+
const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
|
|
365
|
+
statements.push(reactQueryImport);
|
|
366
|
+
const reactQueryTypeImport = t.importDeclaration([
|
|
367
|
+
t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
|
|
368
|
+
t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
|
|
369
|
+
], t.stringLiteral('@tanstack/react-query'));
|
|
370
|
+
reactQueryTypeImport.importKind = 'type';
|
|
371
|
+
statements.push(reactQueryTypeImport);
|
|
372
|
+
}
|
|
373
|
+
const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
|
|
374
|
+
statements.push(clientImport);
|
|
375
|
+
const clientTypeImport = t.importDeclaration([
|
|
376
|
+
t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
|
|
377
|
+
], t.stringLiteral('../client'));
|
|
378
|
+
clientTypeImport.importKind = 'type';
|
|
379
|
+
statements.push(clientTypeImport);
|
|
380
|
+
const typesImport = t.importDeclaration([t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
381
|
+
typesImport.importKind = 'type';
|
|
382
|
+
statements.push(typesImport);
|
|
383
|
+
if (useCentralizedKeys) {
|
|
384
|
+
const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
|
|
385
|
+
statements.push(queryKeyImport);
|
|
386
|
+
if (hasRelationships) {
|
|
387
|
+
const scopeTypeImport = t.importDeclaration([
|
|
388
|
+
t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
|
|
389
|
+
], t.stringLiteral('../query-keys'));
|
|
390
|
+
scopeTypeImport.importKind = 'type';
|
|
391
|
+
statements.push(scopeTypeImport);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
395
|
+
reExportDecl.exportKind = 'type';
|
|
396
|
+
statements.push(reExportDecl);
|
|
397
|
+
const queryDocConst = t.variableDeclaration('const', [
|
|
398
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
|
|
399
|
+
t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
|
|
400
|
+
], [])),
|
|
401
|
+
]);
|
|
402
|
+
statements.push(t.exportNamedDeclaration(queryDocConst));
|
|
403
|
+
const pkTypeAnnotation = pkTsType === 'string'
|
|
404
|
+
? t.tsStringKeyword()
|
|
405
|
+
: pkTsType === 'number'
|
|
406
|
+
? t.tsNumberKeyword()
|
|
407
|
+
: t.tsTypeReference(t.identifier(pkTsType));
|
|
408
|
+
const variablesInterfaceBody = t.tsInterfaceBody([
|
|
409
|
+
t.tsPropertySignature(t.identifier(pkName), t.tsTypeAnnotation(pkTypeAnnotation)),
|
|
410
|
+
]);
|
|
411
|
+
const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(singularName)}QueryVariables`), null, null, variablesInterfaceBody);
|
|
412
|
+
statements.push(t.exportNamedDeclaration(variablesInterface));
|
|
413
|
+
const resultInterfaceBody = t.tsInterfaceBody([
|
|
414
|
+
t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(t.tsUnionType([
|
|
415
|
+
t.tsTypeReference(t.identifier(typeName)),
|
|
416
|
+
t.tsNullKeyword(),
|
|
417
|
+
]))),
|
|
418
|
+
]);
|
|
419
|
+
const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(singularName)}QueryResult`), null, null, resultInterfaceBody);
|
|
420
|
+
statements.push(t.exportNamedDeclaration(resultInterface));
|
|
421
|
+
if (useCentralizedKeys) {
|
|
422
|
+
const queryKeyConst = t.variableDeclaration('const', [
|
|
423
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('detail'))),
|
|
424
|
+
]);
|
|
425
|
+
const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
|
|
426
|
+
addJSDocComment(queryKeyExport, [
|
|
427
|
+
'Query key factory - re-exported from query-keys.ts',
|
|
428
|
+
]);
|
|
429
|
+
statements.push(queryKeyExport);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
const queryKeyArrow = t.arrowFunctionExpression([typedParam(pkName, pkTypeAnnotation)], t.tsAsExpression(t.arrayExpression([
|
|
433
|
+
t.stringLiteral(typeName.toLowerCase()),
|
|
434
|
+
t.stringLiteral('detail'),
|
|
435
|
+
t.identifier(pkName),
|
|
436
|
+
]), t.tsTypeReference(t.identifier('const'))));
|
|
437
|
+
const queryKeyConst = t.variableDeclaration('const', [
|
|
438
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
|
|
439
|
+
]);
|
|
440
|
+
statements.push(t.exportNamedDeclaration(queryKeyConst));
|
|
293
441
|
}
|
|
294
|
-
imports.push(createImport({
|
|
295
|
-
moduleSpecifier: '../client',
|
|
296
|
-
namedImports: ['execute'],
|
|
297
|
-
typeOnlyNamedImports: ['ExecuteOptions'],
|
|
298
|
-
}), createImport({
|
|
299
|
-
moduleSpecifier: '../types',
|
|
300
|
-
typeOnlyNamedImports: [typeName],
|
|
301
|
-
}));
|
|
302
|
-
sourceFile.addImportDeclarations(imports);
|
|
303
|
-
// Re-export entity type
|
|
304
|
-
sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
|
|
305
|
-
// Add section comment
|
|
306
|
-
sourceFile.addStatements('\n// ============================================================================');
|
|
307
|
-
sourceFile.addStatements('// GraphQL Document');
|
|
308
|
-
sourceFile.addStatements('// ============================================================================\n');
|
|
309
|
-
// Add query document constant
|
|
310
|
-
sourceFile.addVariableStatement(createConst(`${queryName}QueryDocument`, '`\n' + queryDocument + '`'));
|
|
311
|
-
// Add section comment
|
|
312
|
-
sourceFile.addStatements('\n// ============================================================================');
|
|
313
|
-
sourceFile.addStatements('// Types');
|
|
314
|
-
sourceFile.addStatements('// ============================================================================\n');
|
|
315
|
-
// Variables interface - use dynamic PK field name and type
|
|
316
|
-
sourceFile.addInterface(createInterface(`${ucFirst(singularName)}QueryVariables`, [
|
|
317
|
-
{ name: pkName, type: pkTsType },
|
|
318
|
-
]));
|
|
319
|
-
// Result interface
|
|
320
|
-
sourceFile.addInterface(createInterface(`${ucFirst(singularName)}QueryResult`, [
|
|
321
|
-
{ name: queryName, type: `${typeName} | null` },
|
|
322
|
-
]));
|
|
323
|
-
// Add section comment
|
|
324
|
-
sourceFile.addStatements('\n// ============================================================================');
|
|
325
|
-
sourceFile.addStatements('// Query Key');
|
|
326
|
-
sourceFile.addStatements('// ============================================================================\n');
|
|
327
|
-
// Query key factory - use dynamic PK field name and type
|
|
328
|
-
sourceFile.addVariableStatement(createConst(`${queryName}QueryKey`, `(${pkName}: ${pkTsType}) =>
|
|
329
|
-
['${typeName.toLowerCase()}', 'detail', ${pkName}] as const`));
|
|
330
|
-
// Add React Query hook section (only if enabled)
|
|
331
442
|
if (reactQueryEnabled) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
443
|
+
const hookBodyStatements = [];
|
|
444
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
445
|
+
hookBodyStatements.push(t.variableDeclaration('const', [
|
|
446
|
+
t.variableDeclarator(t.objectPattern([
|
|
447
|
+
t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
|
|
448
|
+
t.restElement(t.identifier('queryOptions')),
|
|
449
|
+
]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
|
|
450
|
+
]));
|
|
451
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
452
|
+
t.objectExpression([
|
|
453
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
|
|
454
|
+
t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
|
|
455
|
+
t.identifier('scope'),
|
|
456
|
+
])),
|
|
457
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
|
|
458
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
459
|
+
t.identifier('variables'),
|
|
460
|
+
]))),
|
|
461
|
+
t.spreadElement(t.identifier('queryOptions')),
|
|
462
|
+
]),
|
|
463
|
+
])));
|
|
464
|
+
}
|
|
465
|
+
else if (useCentralizedKeys) {
|
|
466
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
467
|
+
t.objectExpression([
|
|
468
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
|
|
469
|
+
t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
|
|
470
|
+
])),
|
|
471
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
|
|
472
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
473
|
+
t.identifier('variables'),
|
|
474
|
+
]))),
|
|
475
|
+
t.spreadElement(t.identifier('options')),
|
|
476
|
+
]),
|
|
477
|
+
])));
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
481
|
+
t.objectExpression([
|
|
482
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
|
|
483
|
+
t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
|
|
484
|
+
])),
|
|
485
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
|
|
486
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
487
|
+
t.identifier('variables'),
|
|
488
|
+
]))),
|
|
489
|
+
t.spreadElement(t.identifier('options')),
|
|
490
|
+
]),
|
|
491
|
+
])));
|
|
492
|
+
}
|
|
493
|
+
const hookParams = [
|
|
494
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
|
|
495
|
+
];
|
|
496
|
+
let optionsTypeStr;
|
|
497
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
498
|
+
optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
|
|
502
|
+
}
|
|
503
|
+
const optionsParam = t.identifier('options');
|
|
504
|
+
optionsParam.optional = true;
|
|
505
|
+
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
|
|
506
|
+
hookParams.push(optionsParam);
|
|
507
|
+
const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
|
|
508
|
+
const hookExport = t.exportNamedDeclaration(hookFunc);
|
|
509
|
+
const docLines = [
|
|
510
|
+
`Query hook for fetching a single ${typeName}`,
|
|
511
|
+
'',
|
|
512
|
+
'@example',
|
|
513
|
+
'```tsx',
|
|
514
|
+
`const { data, isLoading } = ${hookName}({ ${pkName}: 'some-id' });`,
|
|
515
|
+
'```',
|
|
516
|
+
];
|
|
517
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
518
|
+
docLines.push('');
|
|
519
|
+
docLines.push('@example With scope for hierarchical cache invalidation');
|
|
520
|
+
docLines.push('```tsx');
|
|
521
|
+
docLines.push(`const { data } = ${hookName}(`);
|
|
522
|
+
docLines.push(` { ${pkName}: 'some-id' },`);
|
|
523
|
+
docLines.push(" { scope: { parentId: 'parent-id' } }");
|
|
524
|
+
docLines.push(');');
|
|
525
|
+
docLines.push('```');
|
|
526
|
+
}
|
|
527
|
+
addJSDocComment(hookExport, docLines);
|
|
528
|
+
statements.push(hookExport);
|
|
371
529
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
description: `Fetch a single ${typeName} by primary key without React hooks
|
|
398
|
-
|
|
399
|
-
@example
|
|
400
|
-
\`\`\`ts
|
|
401
|
-
const data = await fetch${ucFirst(singularName)}Query(${pkTsType === 'string' ? "'value-here'" : '123'});
|
|
402
|
-
\`\`\``,
|
|
403
|
-
},
|
|
404
|
-
],
|
|
405
|
-
});
|
|
406
|
-
// Prefetch function (for SSR/QueryClient) - only if React Query is enabled, use dynamic PK
|
|
530
|
+
const fetchFuncBody = t.blockStatement([
|
|
531
|
+
t.returnStatement(t.callExpression(t.identifier('execute'), [
|
|
532
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
533
|
+
t.identifier('variables'),
|
|
534
|
+
t.identifier('options'),
|
|
535
|
+
])),
|
|
536
|
+
]);
|
|
537
|
+
const fetchFunc = t.functionDeclaration(t.identifier(`fetch${ucFirst(singularName)}Query`), [
|
|
538
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
|
|
539
|
+
typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
|
|
540
|
+
], fetchFuncBody);
|
|
541
|
+
fetchFunc.async = true;
|
|
542
|
+
fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
|
|
543
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
|
|
544
|
+
])));
|
|
545
|
+
const fetchExport = t.exportNamedDeclaration(fetchFunc);
|
|
546
|
+
addJSDocComment(fetchExport, [
|
|
547
|
+
`Fetch a single ${typeName} without React hooks`,
|
|
548
|
+
'',
|
|
549
|
+
'@example',
|
|
550
|
+
'```ts',
|
|
551
|
+
`const data = await fetch${ucFirst(singularName)}Query({ ${pkName}: 'some-id' });`,
|
|
552
|
+
'```',
|
|
553
|
+
]);
|
|
554
|
+
statements.push(fetchExport);
|
|
407
555
|
if (reactQueryEnabled) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
556
|
+
const prefetchParams = [
|
|
557
|
+
typedParam('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
|
|
558
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
|
|
559
|
+
];
|
|
560
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
561
|
+
prefetchParams.push(typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
|
|
562
|
+
}
|
|
563
|
+
prefetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
|
|
564
|
+
let prefetchQueryKeyExpr;
|
|
565
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
566
|
+
prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
|
|
567
|
+
t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
|
|
568
|
+
t.identifier('scope'),
|
|
569
|
+
]);
|
|
570
|
+
}
|
|
571
|
+
else if (useCentralizedKeys) {
|
|
572
|
+
prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
|
|
576
|
+
}
|
|
577
|
+
const prefetchFuncBody = t.blockStatement([
|
|
578
|
+
t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
|
|
579
|
+
t.objectExpression([
|
|
580
|
+
t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
|
|
581
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
|
|
582
|
+
t.identifier(`${queryName}QueryDocument`),
|
|
583
|
+
t.identifier('variables'),
|
|
584
|
+
t.identifier('options'),
|
|
585
|
+
]))),
|
|
586
|
+
]),
|
|
587
|
+
]))),
|
|
588
|
+
]);
|
|
589
|
+
const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${ucFirst(singularName)}Query`), prefetchParams, prefetchFuncBody);
|
|
590
|
+
prefetchFunc.async = true;
|
|
591
|
+
prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
|
|
592
|
+
const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
|
|
593
|
+
addJSDocComment(prefetchExport, [
|
|
594
|
+
`Prefetch a single ${typeName} for SSR or cache warming`,
|
|
595
|
+
'',
|
|
596
|
+
'@example',
|
|
597
|
+
'```ts',
|
|
598
|
+
`await prefetch${ucFirst(singularName)}Query(queryClient, { ${pkName}: 'some-id' });`,
|
|
599
|
+
'```',
|
|
600
|
+
]);
|
|
601
|
+
statements.push(prefetchExport);
|
|
441
602
|
}
|
|
603
|
+
const code = generateCode(statements);
|
|
604
|
+
const headerText = reactQueryEnabled
|
|
605
|
+
? `Single item query hook for ${typeName}`
|
|
606
|
+
: `Single item query functions for ${typeName}`;
|
|
607
|
+
const content = getGeneratedFileHeader(headerText) + '\n\n' + code;
|
|
442
608
|
return {
|
|
443
609
|
fileName: getSingleQueryFileName(table),
|
|
444
|
-
content
|
|
610
|
+
content,
|
|
445
611
|
};
|
|
446
612
|
}
|
|
447
|
-
// ============================================================================
|
|
448
|
-
// Batch generator
|
|
449
|
-
// ============================================================================
|
|
450
|
-
/**
|
|
451
|
-
* Generate all query hook files for all tables
|
|
452
|
-
*/
|
|
453
613
|
export function generateAllQueryHooks(tables, options = {}) {
|
|
454
614
|
const files = [];
|
|
455
615
|
for (const table of tables) {
|