@constructive-io/graphql-codegen 2.23.3 → 2.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -2
- package/cli/codegen/babel-ast.d.ts +53 -0
- package/cli/codegen/babel-ast.js +160 -0
- package/cli/codegen/barrel.d.ts +7 -2
- package/cli/codegen/barrel.js +193 -102
- package/cli/codegen/client.js +61 -0
- package/cli/codegen/custom-mutations.d.ts +2 -12
- package/cli/codegen/custom-mutations.js +116 -124
- package/cli/codegen/custom-queries.d.ts +2 -10
- package/cli/codegen/custom-queries.js +236 -335
- package/cli/codegen/gql-ast.js +22 -1
- package/cli/codegen/index.d.ts +3 -0
- package/cli/codegen/index.js +73 -3
- package/cli/codegen/invalidation.d.ts +20 -0
- package/cli/codegen/invalidation.js +327 -0
- package/cli/codegen/mutation-keys.d.ts +24 -0
- package/cli/codegen/mutation-keys.js +247 -0
- package/cli/codegen/mutations.d.ts +5 -19
- package/cli/codegen/mutations.js +385 -383
- package/cli/codegen/orm/barrel.d.ts +1 -1
- package/cli/codegen/orm/barrel.js +42 -10
- package/cli/codegen/orm/client-generator.d.ts +1 -19
- package/cli/codegen/orm/client-generator.js +108 -77
- package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
- package/cli/codegen/orm/custom-ops-generator.js +192 -235
- package/cli/codegen/orm/input-types-generator.d.ts +13 -1
- package/cli/codegen/orm/input-types-generator.js +425 -147
- package/cli/codegen/orm/model-generator.d.ts +1 -19
- package/cli/codegen/orm/model-generator.js +229 -234
- package/cli/codegen/queries.d.ts +4 -12
- package/cli/codegen/queries.js +660 -390
- package/cli/codegen/query-keys.d.ts +15 -0
- package/cli/codegen/query-keys.js +477 -0
- package/cli/codegen/scalars.js +1 -0
- package/cli/codegen/schema-types-generator.d.ts +15 -10
- package/cli/codegen/schema-types-generator.js +87 -175
- package/cli/codegen/type-resolver.d.ts +1 -30
- package/cli/codegen/type-resolver.js +0 -53
- package/cli/codegen/types.d.ts +1 -1
- package/cli/codegen/types.js +76 -21
- package/cli/codegen/utils.d.ts +6 -0
- package/cli/codegen/utils.js +19 -0
- package/esm/cli/codegen/babel-ast.d.ts +53 -0
- package/esm/cli/codegen/babel-ast.js +111 -0
- package/esm/cli/codegen/barrel.d.ts +7 -2
- package/esm/cli/codegen/barrel.js +161 -103
- package/esm/cli/codegen/client.js +61 -0
- package/esm/cli/codegen/custom-mutations.d.ts +2 -12
- package/esm/cli/codegen/custom-mutations.js +83 -124
- package/esm/cli/codegen/custom-queries.d.ts +2 -10
- package/esm/cli/codegen/custom-queries.js +204 -336
- package/esm/cli/codegen/gql-ast.js +23 -2
- package/esm/cli/codegen/index.d.ts +3 -0
- package/esm/cli/codegen/index.js +69 -2
- package/esm/cli/codegen/invalidation.d.ts +20 -0
- package/esm/cli/codegen/invalidation.js +291 -0
- package/esm/cli/codegen/mutation-keys.d.ts +24 -0
- package/esm/cli/codegen/mutation-keys.js +211 -0
- package/esm/cli/codegen/mutations.d.ts +5 -19
- package/esm/cli/codegen/mutations.js +353 -384
- package/esm/cli/codegen/orm/barrel.d.ts +1 -1
- package/esm/cli/codegen/orm/barrel.js +10 -11
- package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
- package/esm/cli/codegen/orm/client-generator.js +76 -78
- package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
- package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
- package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
- package/esm/cli/codegen/orm/input-types-generator.js +393 -148
- package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
- package/esm/cli/codegen/orm/model-generator.js +197 -235
- package/esm/cli/codegen/queries.d.ts +4 -12
- package/esm/cli/codegen/queries.js +628 -391
- package/esm/cli/codegen/query-keys.d.ts +15 -0
- package/esm/cli/codegen/query-keys.js +441 -0
- package/esm/cli/codegen/scalars.js +1 -0
- package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
- package/esm/cli/codegen/schema-types-generator.js +54 -175
- package/esm/cli/codegen/type-resolver.d.ts +1 -30
- package/esm/cli/codegen/type-resolver.js +0 -49
- package/esm/cli/codegen/types.d.ts +1 -1
- package/esm/cli/codegen/types.js +44 -22
- package/esm/cli/codegen/utils.d.ts +6 -0
- package/esm/cli/codegen/utils.js +18 -0
- package/esm/types/config.d.ts +75 -0
- package/esm/types/config.js +18 -0
- package/package.json +6 -4
- package/types/config.d.ts +75 -0
- package/types/config.js +19 -1
- package/cli/codegen/ts-ast.d.ts +0 -124
- package/cli/codegen/ts-ast.js +0 -280
- package/esm/cli/codegen/ts-ast.d.ts +0 -124
- package/esm/cli/codegen/ts-ast.js +0 -260
|
@@ -1,31 +1,47 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } 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, getConditionTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, getPrimaryKeyInfo, hasValidPrimaryKey, fieldTypeToTs, 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);
|
|
37
|
+
const conditionTypeName = getConditionTypeName(table);
|
|
17
38
|
const orderByTypeName = getOrderByTypeName(table);
|
|
18
39
|
const scalarFields = getScalarFields(table);
|
|
19
|
-
|
|
40
|
+
const keysName = `${lcFirst(typeName)}Keys`;
|
|
41
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
20
42
|
const queryAST = buildListQueryAST({ table });
|
|
21
43
|
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
|
|
44
|
+
const statements = [];
|
|
29
45
|
const filterTypesUsed = new Set();
|
|
30
46
|
for (const field of scalarFields) {
|
|
31
47
|
const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
|
|
@@ -33,47 +49,109 @@ export function generateListQueryHook(table, options = {}) {
|
|
|
33
49
|
filterTypesUsed.add(filterType);
|
|
34
50
|
}
|
|
35
51
|
}
|
|
36
|
-
// Add imports - conditionally include React Query imports
|
|
37
|
-
const imports = [];
|
|
38
52
|
if (reactQueryEnabled) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
|
|
54
|
+
statements.push(reactQueryImport);
|
|
55
|
+
const reactQueryTypeImport = t.importDeclaration([
|
|
56
|
+
t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
|
|
57
|
+
t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
|
|
58
|
+
], t.stringLiteral('@tanstack/react-query'));
|
|
59
|
+
reactQueryTypeImport.importKind = 'type';
|
|
60
|
+
statements.push(reactQueryTypeImport);
|
|
44
61
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
|
|
63
|
+
statements.push(clientImport);
|
|
64
|
+
const clientTypeImport = t.importDeclaration([
|
|
65
|
+
t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
|
|
66
|
+
], t.stringLiteral('../client'));
|
|
67
|
+
clientTypeImport.importKind = 'type';
|
|
68
|
+
statements.push(clientTypeImport);
|
|
69
|
+
const typesImport = t.importDeclaration([
|
|
70
|
+
t.importSpecifier(t.identifier(typeName), t.identifier(typeName)),
|
|
71
|
+
...Array.from(filterTypesUsed).map((ft) => t.importSpecifier(t.identifier(ft), t.identifier(ft))),
|
|
72
|
+
], t.stringLiteral('../types'));
|
|
73
|
+
typesImport.importKind = 'type';
|
|
74
|
+
statements.push(typesImport);
|
|
75
|
+
if (useCentralizedKeys) {
|
|
76
|
+
const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
|
|
77
|
+
statements.push(queryKeyImport);
|
|
78
|
+
if (hasRelationships) {
|
|
79
|
+
const scopeTypeImport = t.importDeclaration([
|
|
80
|
+
t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
|
|
81
|
+
], t.stringLiteral('../query-keys'));
|
|
82
|
+
scopeTypeImport.importKind = 'type';
|
|
83
|
+
statements.push(scopeTypeImport);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
87
|
+
reExportDecl.exportKind = 'type';
|
|
88
|
+
statements.push(reExportDecl);
|
|
89
|
+
const queryDocConst = t.variableDeclaration('const', [
|
|
90
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
|
|
91
|
+
t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
|
|
92
|
+
], [])),
|
|
93
|
+
]);
|
|
94
|
+
statements.push(t.exportNamedDeclaration(queryDocConst));
|
|
67
95
|
const fieldFilters = scalarFields
|
|
68
96
|
.map((field) => {
|
|
69
97
|
const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
|
|
70
98
|
return filterType ? { fieldName: field.name, filterType } : null;
|
|
71
99
|
})
|
|
72
100
|
.filter((f) => f !== null);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
101
|
+
statements.push(createFilterInterfaceDeclaration(filterTypeName, fieldFilters, false));
|
|
102
|
+
// Generate Condition interface (simple equality filter with scalar types)
|
|
103
|
+
// Track non-primitive types (enums) that need to be imported
|
|
104
|
+
const enumTypesUsed = new Set();
|
|
105
|
+
const conditionProperties = scalarFields.map((field) => {
|
|
106
|
+
const tsType = fieldTypeToTs(field.type);
|
|
107
|
+
const isPrimitive = tsType === 'string' ||
|
|
108
|
+
tsType === 'number' ||
|
|
109
|
+
tsType === 'boolean' ||
|
|
110
|
+
tsType === 'unknown' ||
|
|
111
|
+
tsType.endsWith('[]');
|
|
112
|
+
let typeAnnotation;
|
|
113
|
+
if (field.type.isArray) {
|
|
114
|
+
const baseType = tsType.replace('[]', '');
|
|
115
|
+
const isBasePrimitive = baseType === 'string' ||
|
|
116
|
+
baseType === 'number' ||
|
|
117
|
+
baseType === 'boolean' ||
|
|
118
|
+
baseType === 'unknown';
|
|
119
|
+
if (!isBasePrimitive) {
|
|
120
|
+
enumTypesUsed.add(baseType);
|
|
121
|
+
}
|
|
122
|
+
typeAnnotation = t.tsArrayType(baseType === 'string'
|
|
123
|
+
? t.tsStringKeyword()
|
|
124
|
+
: baseType === 'number'
|
|
125
|
+
? t.tsNumberKeyword()
|
|
126
|
+
: baseType === 'boolean'
|
|
127
|
+
? t.tsBooleanKeyword()
|
|
128
|
+
: t.tsTypeReference(t.identifier(baseType)));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
if (!isPrimitive) {
|
|
132
|
+
enumTypesUsed.add(tsType);
|
|
133
|
+
}
|
|
134
|
+
typeAnnotation =
|
|
135
|
+
tsType === 'string'
|
|
136
|
+
? t.tsStringKeyword()
|
|
137
|
+
: tsType === 'number'
|
|
138
|
+
? t.tsNumberKeyword()
|
|
139
|
+
: tsType === 'boolean'
|
|
140
|
+
? t.tsBooleanKeyword()
|
|
141
|
+
: t.tsTypeReference(t.identifier(tsType));
|
|
142
|
+
}
|
|
143
|
+
const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(typeAnnotation));
|
|
144
|
+
prop.optional = true;
|
|
145
|
+
return prop;
|
|
146
|
+
});
|
|
147
|
+
// Add import for enum types if any are used
|
|
148
|
+
if (enumTypesUsed.size > 0) {
|
|
149
|
+
const schemaTypesImport = t.importDeclaration(Array.from(enumTypesUsed).map((et) => t.importSpecifier(t.identifier(et), t.identifier(et))), t.stringLiteral('../schema-types'));
|
|
150
|
+
schemaTypesImport.importKind = 'type';
|
|
151
|
+
statements.push(schemaTypesImport);
|
|
152
|
+
}
|
|
153
|
+
const conditionInterface = t.tsInterfaceDeclaration(t.identifier(conditionTypeName), null, null, t.tsInterfaceBody(conditionProperties));
|
|
154
|
+
statements.push(conditionInterface);
|
|
77
155
|
const orderByValues = [
|
|
78
156
|
...scalarFields.flatMap((f) => [
|
|
79
157
|
`${toScreamingSnake(f.name)}_ASC`,
|
|
@@ -83,378 +161,537 @@ export function generateListQueryHook(table, options = {}) {
|
|
|
83
161
|
'PRIMARY_KEY_ASC',
|
|
84
162
|
'PRIMARY_KEY_DESC',
|
|
85
163
|
];
|
|
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
|
-
|
|
164
|
+
const orderByTypeAlias = t.tsTypeAliasDeclaration(t.identifier(orderByTypeName), null, createUnionType(orderByValues));
|
|
165
|
+
statements.push(orderByTypeAlias);
|
|
166
|
+
const variablesInterfaceBody = t.tsInterfaceBody([
|
|
167
|
+
(() => {
|
|
168
|
+
const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
|
|
169
|
+
p.optional = true;
|
|
170
|
+
return p;
|
|
171
|
+
})(),
|
|
172
|
+
(() => {
|
|
173
|
+
const p = t.tsPropertySignature(t.identifier('last'), t.tsTypeAnnotation(t.tsNumberKeyword()));
|
|
174
|
+
p.optional = true;
|
|
175
|
+
return p;
|
|
176
|
+
})(),
|
|
177
|
+
(() => {
|
|
178
|
+
const p = t.tsPropertySignature(t.identifier('offset'), t.tsTypeAnnotation(t.tsNumberKeyword()));
|
|
179
|
+
p.optional = true;
|
|
180
|
+
return p;
|
|
181
|
+
})(),
|
|
182
|
+
(() => {
|
|
183
|
+
const p = t.tsPropertySignature(t.identifier('before'), t.tsTypeAnnotation(t.tsStringKeyword()));
|
|
184
|
+
p.optional = true;
|
|
185
|
+
return p;
|
|
186
|
+
})(),
|
|
187
|
+
(() => {
|
|
188
|
+
const p = t.tsPropertySignature(t.identifier('after'), t.tsTypeAnnotation(t.tsStringKeyword()));
|
|
189
|
+
p.optional = true;
|
|
190
|
+
return p;
|
|
191
|
+
})(),
|
|
192
|
+
(() => {
|
|
193
|
+
const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterTypeName))));
|
|
194
|
+
p.optional = true;
|
|
195
|
+
return p;
|
|
196
|
+
})(),
|
|
197
|
+
(() => {
|
|
198
|
+
const p = t.tsPropertySignature(t.identifier('condition'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(conditionTypeName))));
|
|
199
|
+
p.optional = true;
|
|
200
|
+
return p;
|
|
201
|
+
})(),
|
|
202
|
+
(() => {
|
|
203
|
+
const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByTypeName)))));
|
|
204
|
+
p.optional = true;
|
|
205
|
+
return p;
|
|
206
|
+
})(),
|
|
207
|
+
]);
|
|
208
|
+
const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(pluralName)}QueryVariables`), null, null, variablesInterfaceBody);
|
|
209
|
+
statements.push(t.exportNamedDeclaration(variablesInterface));
|
|
210
|
+
const pageInfoType = t.tsTypeLiteral([
|
|
211
|
+
t.tsPropertySignature(t.identifier('hasNextPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
|
|
212
|
+
t.tsPropertySignature(t.identifier('hasPreviousPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
|
|
213
|
+
t.tsPropertySignature(t.identifier('startCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
|
|
214
|
+
t.tsPropertySignature(t.identifier('endCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
|
|
215
|
+
]);
|
|
216
|
+
const resultType = t.tsTypeLiteral([
|
|
217
|
+
t.tsPropertySignature(t.identifier('totalCount'), t.tsTypeAnnotation(t.tsNumberKeyword())),
|
|
218
|
+
t.tsPropertySignature(t.identifier('nodes'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(typeName))))),
|
|
219
|
+
t.tsPropertySignature(t.identifier('pageInfo'), t.tsTypeAnnotation(pageInfoType)),
|
|
220
|
+
]);
|
|
221
|
+
const resultInterfaceBody = t.tsInterfaceBody([
|
|
222
|
+
t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(resultType)),
|
|
223
|
+
]);
|
|
224
|
+
const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(pluralName)}QueryResult`), null, null, resultInterfaceBody);
|
|
225
|
+
statements.push(t.exportNamedDeclaration(resultInterface));
|
|
226
|
+
if (useCentralizedKeys) {
|
|
227
|
+
const queryKeyConst = t.variableDeclaration('const', [
|
|
228
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('list'))),
|
|
229
|
+
]);
|
|
230
|
+
const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
|
|
231
|
+
addJSDocComment(queryKeyExport, [
|
|
232
|
+
'Query key factory - re-exported from query-keys.ts',
|
|
233
|
+
]);
|
|
234
|
+
statements.push(queryKeyExport);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const queryKeyArrow = t.arrowFunctionExpression([
|
|
238
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
|
|
239
|
+
], t.tsAsExpression(t.arrayExpression([
|
|
240
|
+
t.stringLiteral(typeName.toLowerCase()),
|
|
241
|
+
t.stringLiteral('list'),
|
|
242
|
+
t.identifier('variables'),
|
|
243
|
+
]), t.tsTypeReference(t.identifier('const'))));
|
|
244
|
+
const queryKeyConst = t.variableDeclaration('const', [
|
|
245
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
|
|
246
|
+
]);
|
|
247
|
+
statements.push(t.exportNamedDeclaration(queryKeyConst));
|
|
248
|
+
}
|
|
120
249
|
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
|
-
|
|
250
|
+
const hookBodyStatements = [];
|
|
251
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
252
|
+
hookBodyStatements.push(t.variableDeclaration('const', [
|
|
253
|
+
t.variableDeclarator(t.objectPattern([
|
|
254
|
+
t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
|
|
255
|
+
t.restElement(t.identifier('queryOptions')),
|
|
256
|
+
]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
|
|
257
|
+
]));
|
|
258
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
259
|
+
t.objectExpression([
|
|
260
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')])),
|
|
261
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
|
|
262
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
|
|
263
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
|
|
264
|
+
]))),
|
|
265
|
+
t.spreadElement(t.identifier('queryOptions')),
|
|
266
|
+
]),
|
|
267
|
+
])));
|
|
268
|
+
}
|
|
269
|
+
else if (useCentralizedKeys) {
|
|
270
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
271
|
+
t.objectExpression([
|
|
272
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')])),
|
|
273
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
|
|
274
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
|
|
275
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
|
|
276
|
+
]))),
|
|
277
|
+
t.spreadElement(t.identifier('options')),
|
|
278
|
+
]),
|
|
279
|
+
])));
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
283
|
+
t.objectExpression([
|
|
284
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
|
|
285
|
+
t.identifier('variables'),
|
|
286
|
+
])),
|
|
287
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
|
|
288
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
|
|
289
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
|
|
290
|
+
]))),
|
|
291
|
+
t.spreadElement(t.identifier('options')),
|
|
292
|
+
]),
|
|
293
|
+
])));
|
|
294
|
+
}
|
|
295
|
+
const hookParams = [
|
|
296
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
|
|
297
|
+
];
|
|
298
|
+
let optionsTypeStr;
|
|
299
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
300
|
+
optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
|
|
304
|
+
}
|
|
305
|
+
const optionsParam = t.identifier('options');
|
|
306
|
+
optionsParam.optional = true;
|
|
307
|
+
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
|
|
308
|
+
hookParams.push(optionsParam);
|
|
309
|
+
const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
|
|
310
|
+
const hookExport = t.exportNamedDeclaration(hookFunc);
|
|
311
|
+
const docLines = [
|
|
312
|
+
`Query hook for fetching ${typeName} list`,
|
|
313
|
+
'',
|
|
314
|
+
'@example',
|
|
315
|
+
'```tsx',
|
|
316
|
+
`const { data, isLoading } = ${hookName}({`,
|
|
317
|
+
' first: 10,',
|
|
318
|
+
' filter: { name: { equalTo: "example" } },',
|
|
319
|
+
" orderBy: ['CREATED_AT_DESC'],",
|
|
320
|
+
'});',
|
|
321
|
+
'```',
|
|
322
|
+
];
|
|
323
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
324
|
+
docLines.push('');
|
|
325
|
+
docLines.push('@example With scope for hierarchical cache invalidation');
|
|
326
|
+
docLines.push('```tsx');
|
|
327
|
+
docLines.push(`const { data } = ${hookName}(`);
|
|
328
|
+
docLines.push(' { first: 10 },');
|
|
329
|
+
docLines.push(" { scope: { parentId: 'parent-id' } }");
|
|
330
|
+
docLines.push(');');
|
|
331
|
+
docLines.push('```');
|
|
332
|
+
}
|
|
333
|
+
addJSDocComment(hookExport, docLines);
|
|
334
|
+
statements.push(hookExport);
|
|
163
335
|
}
|
|
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
|
-
@example
|
|
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
|
|
336
|
+
const fetchFuncBody = t.blockStatement([
|
|
337
|
+
t.returnStatement(createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
|
|
338
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
|
|
339
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
|
|
340
|
+
])),
|
|
341
|
+
]);
|
|
342
|
+
const fetchFunc = t.functionDeclaration(t.identifier(`fetch${ucFirst(pluralName)}Query`), [
|
|
343
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
|
|
344
|
+
typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
|
|
345
|
+
], fetchFuncBody);
|
|
346
|
+
fetchFunc.async = true;
|
|
347
|
+
fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
|
|
348
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
|
|
349
|
+
])));
|
|
350
|
+
const fetchExport = t.exportNamedDeclaration(fetchFunc);
|
|
351
|
+
addJSDocComment(fetchExport, [
|
|
352
|
+
`Fetch ${typeName} list without React hooks`,
|
|
353
|
+
'',
|
|
354
|
+
'@example',
|
|
355
|
+
'```ts',
|
|
356
|
+
'// Direct fetch',
|
|
357
|
+
`const data = await fetch${ucFirst(pluralName)}Query({ first: 10 });`,
|
|
358
|
+
'',
|
|
359
|
+
'// With QueryClient',
|
|
360
|
+
'const data = await queryClient.fetchQuery({',
|
|
361
|
+
` queryKey: ${queryName}QueryKey(variables),`,
|
|
362
|
+
` queryFn: () => fetch${ucFirst(pluralName)}Query(variables),`,
|
|
363
|
+
'});',
|
|
364
|
+
'```',
|
|
365
|
+
]);
|
|
366
|
+
statements.push(fetchExport);
|
|
210
367
|
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
|
-
|
|
368
|
+
const prefetchParams = [
|
|
369
|
+
typedParam('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
|
|
370
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)), true),
|
|
371
|
+
];
|
|
372
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
373
|
+
prefetchParams.push(typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
|
|
374
|
+
}
|
|
375
|
+
prefetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
|
|
376
|
+
let prefetchQueryKeyExpr;
|
|
377
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
378
|
+
prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')]);
|
|
379
|
+
}
|
|
380
|
+
else if (useCentralizedKeys) {
|
|
381
|
+
prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')]);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.identifier('variables')]);
|
|
385
|
+
}
|
|
386
|
+
const prefetchFuncBody = t.blockStatement([
|
|
387
|
+
t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
|
|
388
|
+
t.objectExpression([
|
|
389
|
+
t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
|
|
390
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
|
|
391
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryResult`)),
|
|
392
|
+
t.tsTypeReference(t.identifier(`${ucFirst(pluralName)}QueryVariables`)),
|
|
393
|
+
]))),
|
|
394
|
+
]),
|
|
395
|
+
]))),
|
|
396
|
+
]);
|
|
397
|
+
const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${ucFirst(pluralName)}Query`), prefetchParams, prefetchFuncBody);
|
|
398
|
+
prefetchFunc.async = true;
|
|
399
|
+
prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
|
|
400
|
+
const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
|
|
401
|
+
addJSDocComment(prefetchExport, [
|
|
402
|
+
`Prefetch ${typeName} list for SSR or cache warming`,
|
|
403
|
+
'',
|
|
404
|
+
'@example',
|
|
405
|
+
'```ts',
|
|
406
|
+
`await prefetch${ucFirst(pluralName)}Query(queryClient, { first: 10 });`,
|
|
407
|
+
'```',
|
|
408
|
+
]);
|
|
409
|
+
statements.push(prefetchExport);
|
|
251
410
|
}
|
|
411
|
+
const code = generateCode(statements);
|
|
412
|
+
const headerText = reactQueryEnabled
|
|
413
|
+
? `List query hook for ${typeName}`
|
|
414
|
+
: `List query functions for ${typeName}`;
|
|
415
|
+
const content = getGeneratedFileHeader(headerText) + '\n\n' + code;
|
|
252
416
|
return {
|
|
253
417
|
fileName: getListQueryFileName(table),
|
|
254
|
-
content
|
|
418
|
+
content,
|
|
255
419
|
};
|
|
256
420
|
}
|
|
257
|
-
// ============================================================================
|
|
258
|
-
// Single item query hook generator
|
|
259
|
-
// ============================================================================
|
|
260
|
-
/**
|
|
261
|
-
* Generate single item query hook file content using AST
|
|
262
|
-
*/
|
|
263
421
|
export function generateSingleQueryHook(table, options = {}) {
|
|
264
|
-
|
|
265
|
-
|
|
422
|
+
// Skip tables with composite keys - they are handled as custom queries
|
|
423
|
+
if (!hasValidPrimaryKey(table)) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
|
|
266
427
|
const { typeName, singularName } = getTableNames(table);
|
|
267
428
|
const hookName = getSingleQueryHookName(table);
|
|
268
429
|
const queryName = getSingleRowQueryName(table);
|
|
269
|
-
|
|
430
|
+
const keysName = `${lcFirst(typeName)}Keys`;
|
|
431
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
270
432
|
const pkFields = getPrimaryKeyInfo(table);
|
|
271
|
-
// For simplicity, use first PK field (most common case)
|
|
272
|
-
// Composite PKs would need more complex handling
|
|
273
433
|
const pkField = pkFields[0];
|
|
274
434
|
const pkName = pkField.name;
|
|
275
435
|
const pkTsType = pkField.tsType;
|
|
276
|
-
// Generate GraphQL document via AST
|
|
277
436
|
const queryAST = buildSingleQueryAST({ table });
|
|
278
437
|
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 = [];
|
|
438
|
+
const statements = [];
|
|
287
439
|
if (reactQueryEnabled) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
440
|
+
const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
|
|
441
|
+
statements.push(reactQueryImport);
|
|
442
|
+
const reactQueryTypeImport = t.importDeclaration([
|
|
443
|
+
t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
|
|
444
|
+
t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
|
|
445
|
+
], t.stringLiteral('@tanstack/react-query'));
|
|
446
|
+
reactQueryTypeImport.importKind = 'type';
|
|
447
|
+
statements.push(reactQueryTypeImport);
|
|
448
|
+
}
|
|
449
|
+
const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
|
|
450
|
+
statements.push(clientImport);
|
|
451
|
+
const clientTypeImport = t.importDeclaration([
|
|
452
|
+
t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
|
|
453
|
+
], t.stringLiteral('../client'));
|
|
454
|
+
clientTypeImport.importKind = 'type';
|
|
455
|
+
statements.push(clientTypeImport);
|
|
456
|
+
const typesImport = t.importDeclaration([t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
457
|
+
typesImport.importKind = 'type';
|
|
458
|
+
statements.push(typesImport);
|
|
459
|
+
if (useCentralizedKeys) {
|
|
460
|
+
const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
|
|
461
|
+
statements.push(queryKeyImport);
|
|
462
|
+
if (hasRelationships) {
|
|
463
|
+
const scopeTypeImport = t.importDeclaration([
|
|
464
|
+
t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
|
|
465
|
+
], t.stringLiteral('../query-keys'));
|
|
466
|
+
scopeTypeImport.importKind = 'type';
|
|
467
|
+
statements.push(scopeTypeImport);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
|
|
471
|
+
reExportDecl.exportKind = 'type';
|
|
472
|
+
statements.push(reExportDecl);
|
|
473
|
+
const queryDocConst = t.variableDeclaration('const', [
|
|
474
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
|
|
475
|
+
t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
|
|
476
|
+
], [])),
|
|
477
|
+
]);
|
|
478
|
+
statements.push(t.exportNamedDeclaration(queryDocConst));
|
|
479
|
+
const pkTypeAnnotation = pkTsType === 'string'
|
|
480
|
+
? t.tsStringKeyword()
|
|
481
|
+
: pkTsType === 'number'
|
|
482
|
+
? t.tsNumberKeyword()
|
|
483
|
+
: t.tsTypeReference(t.identifier(pkTsType));
|
|
484
|
+
const variablesInterfaceBody = t.tsInterfaceBody([
|
|
485
|
+
t.tsPropertySignature(t.identifier(pkName), t.tsTypeAnnotation(pkTypeAnnotation)),
|
|
486
|
+
]);
|
|
487
|
+
const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(singularName)}QueryVariables`), null, null, variablesInterfaceBody);
|
|
488
|
+
statements.push(t.exportNamedDeclaration(variablesInterface));
|
|
489
|
+
const resultInterfaceBody = t.tsInterfaceBody([
|
|
490
|
+
t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(t.tsUnionType([
|
|
491
|
+
t.tsTypeReference(t.identifier(typeName)),
|
|
492
|
+
t.tsNullKeyword(),
|
|
493
|
+
]))),
|
|
494
|
+
]);
|
|
495
|
+
const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${ucFirst(singularName)}QueryResult`), null, null, resultInterfaceBody);
|
|
496
|
+
statements.push(t.exportNamedDeclaration(resultInterface));
|
|
497
|
+
if (useCentralizedKeys) {
|
|
498
|
+
const queryKeyConst = t.variableDeclaration('const', [
|
|
499
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('detail'))),
|
|
500
|
+
]);
|
|
501
|
+
const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
|
|
502
|
+
addJSDocComment(queryKeyExport, [
|
|
503
|
+
'Query key factory - re-exported from query-keys.ts',
|
|
504
|
+
]);
|
|
505
|
+
statements.push(queryKeyExport);
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
const queryKeyArrow = t.arrowFunctionExpression([typedParam(pkName, pkTypeAnnotation)], t.tsAsExpression(t.arrayExpression([
|
|
509
|
+
t.stringLiteral(typeName.toLowerCase()),
|
|
510
|
+
t.stringLiteral('detail'),
|
|
511
|
+
t.identifier(pkName),
|
|
512
|
+
]), t.tsTypeReference(t.identifier('const'))));
|
|
513
|
+
const queryKeyConst = t.variableDeclaration('const', [
|
|
514
|
+
t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
|
|
515
|
+
]);
|
|
516
|
+
statements.push(t.exportNamedDeclaration(queryKeyConst));
|
|
293
517
|
}
|
|
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
518
|
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
|
-
|
|
519
|
+
const hookBodyStatements = [];
|
|
520
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
521
|
+
hookBodyStatements.push(t.variableDeclaration('const', [
|
|
522
|
+
t.variableDeclarator(t.objectPattern([
|
|
523
|
+
t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
|
|
524
|
+
t.restElement(t.identifier('queryOptions')),
|
|
525
|
+
]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
|
|
526
|
+
]));
|
|
527
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
528
|
+
t.objectExpression([
|
|
529
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
|
|
530
|
+
t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
|
|
531
|
+
t.identifier('scope'),
|
|
532
|
+
])),
|
|
533
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
|
|
534
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
|
|
535
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
|
|
536
|
+
]))),
|
|
537
|
+
t.spreadElement(t.identifier('queryOptions')),
|
|
538
|
+
]),
|
|
539
|
+
])));
|
|
540
|
+
}
|
|
541
|
+
else if (useCentralizedKeys) {
|
|
542
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
543
|
+
t.objectExpression([
|
|
544
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
|
|
545
|
+
t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
|
|
546
|
+
])),
|
|
547
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
|
|
548
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
|
|
549
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
|
|
550
|
+
]))),
|
|
551
|
+
t.spreadElement(t.identifier('options')),
|
|
552
|
+
]),
|
|
553
|
+
])));
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
|
|
557
|
+
t.objectExpression([
|
|
558
|
+
t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
|
|
559
|
+
t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
|
|
560
|
+
])),
|
|
561
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
|
|
562
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
|
|
563
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
|
|
564
|
+
]))),
|
|
565
|
+
t.spreadElement(t.identifier('options')),
|
|
566
|
+
]),
|
|
567
|
+
])));
|
|
568
|
+
}
|
|
569
|
+
const hookParams = [
|
|
570
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
|
|
571
|
+
];
|
|
572
|
+
let optionsTypeStr;
|
|
573
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
574
|
+
optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
optionsTypeStr = `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
|
|
578
|
+
}
|
|
579
|
+
const optionsParam = t.identifier('options');
|
|
580
|
+
optionsParam.optional = true;
|
|
581
|
+
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
|
|
582
|
+
hookParams.push(optionsParam);
|
|
583
|
+
const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
|
|
584
|
+
const hookExport = t.exportNamedDeclaration(hookFunc);
|
|
585
|
+
const docLines = [
|
|
586
|
+
`Query hook for fetching a single ${typeName}`,
|
|
587
|
+
'',
|
|
588
|
+
'@example',
|
|
589
|
+
'```tsx',
|
|
590
|
+
`const { data, isLoading } = ${hookName}({ ${pkName}: 'some-id' });`,
|
|
591
|
+
'```',
|
|
592
|
+
];
|
|
593
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
594
|
+
docLines.push('');
|
|
595
|
+
docLines.push('@example With scope for hierarchical cache invalidation');
|
|
596
|
+
docLines.push('```tsx');
|
|
597
|
+
docLines.push(`const { data } = ${hookName}(`);
|
|
598
|
+
docLines.push(` { ${pkName}: 'some-id' },`);
|
|
599
|
+
docLines.push(" { scope: { parentId: 'parent-id' } }");
|
|
600
|
+
docLines.push(');');
|
|
601
|
+
docLines.push('```');
|
|
602
|
+
}
|
|
603
|
+
addJSDocComment(hookExport, docLines);
|
|
604
|
+
statements.push(hookExport);
|
|
371
605
|
}
|
|
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
|
|
606
|
+
const fetchFuncBody = t.blockStatement([
|
|
607
|
+
t.returnStatement(createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
|
|
608
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
|
|
609
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
|
|
610
|
+
])),
|
|
611
|
+
]);
|
|
612
|
+
const fetchFunc = t.functionDeclaration(t.identifier(`fetch${ucFirst(singularName)}Query`), [
|
|
613
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
|
|
614
|
+
typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
|
|
615
|
+
], fetchFuncBody);
|
|
616
|
+
fetchFunc.async = true;
|
|
617
|
+
fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
|
|
618
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
|
|
619
|
+
])));
|
|
620
|
+
const fetchExport = t.exportNamedDeclaration(fetchFunc);
|
|
621
|
+
addJSDocComment(fetchExport, [
|
|
622
|
+
`Fetch a single ${typeName} without React hooks`,
|
|
623
|
+
'',
|
|
624
|
+
'@example',
|
|
625
|
+
'```ts',
|
|
626
|
+
`const data = await fetch${ucFirst(singularName)}Query({ ${pkName}: 'some-id' });`,
|
|
627
|
+
'```',
|
|
628
|
+
]);
|
|
629
|
+
statements.push(fetchExport);
|
|
407
630
|
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
|
-
});
|
|
631
|
+
const prefetchParams = [
|
|
632
|
+
typedParam('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
|
|
633
|
+
typedParam('variables', t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`))),
|
|
634
|
+
];
|
|
635
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
636
|
+
prefetchParams.push(typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
|
|
637
|
+
}
|
|
638
|
+
prefetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
|
|
639
|
+
let prefetchQueryKeyExpr;
|
|
640
|
+
if (hasRelationships && useCentralizedKeys) {
|
|
641
|
+
prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
|
|
642
|
+
t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
|
|
643
|
+
t.identifier('scope'),
|
|
644
|
+
]);
|
|
645
|
+
}
|
|
646
|
+
else if (useCentralizedKeys) {
|
|
647
|
+
prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
|
|
651
|
+
}
|
|
652
|
+
const prefetchFuncBody = t.blockStatement([
|
|
653
|
+
t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
|
|
654
|
+
t.objectExpression([
|
|
655
|
+
t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
|
|
656
|
+
t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
|
|
657
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryResult`)),
|
|
658
|
+
t.tsTypeReference(t.identifier(`${ucFirst(singularName)}QueryVariables`)),
|
|
659
|
+
]))),
|
|
660
|
+
]),
|
|
661
|
+
]))),
|
|
662
|
+
]);
|
|
663
|
+
const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${ucFirst(singularName)}Query`), prefetchParams, prefetchFuncBody);
|
|
664
|
+
prefetchFunc.async = true;
|
|
665
|
+
prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
|
|
666
|
+
const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
|
|
667
|
+
addJSDocComment(prefetchExport, [
|
|
668
|
+
`Prefetch a single ${typeName} for SSR or cache warming`,
|
|
669
|
+
'',
|
|
670
|
+
'@example',
|
|
671
|
+
'```ts',
|
|
672
|
+
`await prefetch${ucFirst(singularName)}Query(queryClient, { ${pkName}: 'some-id' });`,
|
|
673
|
+
'```',
|
|
674
|
+
]);
|
|
675
|
+
statements.push(prefetchExport);
|
|
441
676
|
}
|
|
677
|
+
const code = generateCode(statements);
|
|
678
|
+
const headerText = reactQueryEnabled
|
|
679
|
+
? `Single item query hook for ${typeName}`
|
|
680
|
+
: `Single item query functions for ${typeName}`;
|
|
681
|
+
const content = getGeneratedFileHeader(headerText) + '\n\n' + code;
|
|
442
682
|
return {
|
|
443
683
|
fileName: getSingleQueryFileName(table),
|
|
444
|
-
content
|
|
684
|
+
content,
|
|
445
685
|
};
|
|
446
686
|
}
|
|
447
|
-
// ============================================================================
|
|
448
|
-
// Batch generator
|
|
449
|
-
// ============================================================================
|
|
450
|
-
/**
|
|
451
|
-
* Generate all query hook files for all tables
|
|
452
|
-
*/
|
|
453
687
|
export function generateAllQueryHooks(tables, options = {}) {
|
|
454
688
|
const files = [];
|
|
455
689
|
for (const table of tables) {
|
|
456
690
|
files.push(generateListQueryHook(table, options));
|
|
457
|
-
|
|
691
|
+
const singleHook = generateSingleQueryHook(table, options);
|
|
692
|
+
if (singleHook) {
|
|
693
|
+
files.push(singleHook);
|
|
694
|
+
}
|
|
458
695
|
}
|
|
459
696
|
return files;
|
|
460
697
|
}
|