@constructive-io/graphql-codegen 2.20.1 → 2.22.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 +15 -3
- package/cli/codegen/barrel.d.ts +4 -1
- package/cli/codegen/barrel.js +18 -12
- package/cli/codegen/client.js +33 -0
- package/cli/codegen/custom-mutations.d.ts +11 -1
- package/cli/codegen/custom-mutations.js +49 -15
- package/cli/codegen/custom-queries.d.ts +8 -0
- package/cli/codegen/custom-queries.js +82 -47
- package/cli/codegen/gql-ast.js +9 -5
- package/cli/codegen/index.js +39 -8
- package/cli/codegen/mutations.d.ts +14 -4
- package/cli/codegen/mutations.js +114 -28
- package/cli/codegen/orm/barrel.js +4 -2
- package/cli/codegen/orm/index.js +17 -0
- package/cli/codegen/orm/input-types-generator.js +83 -29
- package/cli/codegen/orm/model-generator.js +6 -4
- package/cli/codegen/queries.d.ts +7 -3
- package/cli/codegen/queries.js +185 -158
- package/cli/codegen/scalars.d.ts +6 -4
- package/cli/codegen/scalars.js +17 -9
- package/cli/codegen/schema-types-generator.d.ts +26 -0
- package/cli/codegen/schema-types-generator.js +365 -0
- package/cli/codegen/ts-ast.d.ts +3 -1
- package/cli/codegen/ts-ast.js +2 -2
- package/cli/codegen/type-resolver.d.ts +52 -6
- package/cli/codegen/type-resolver.js +97 -19
- package/cli/codegen/types.d.ts +7 -4
- package/cli/codegen/types.js +94 -41
- package/cli/codegen/utils.d.ts +20 -2
- package/cli/codegen/utils.js +32 -7
- package/cli/commands/generate-orm.js +5 -5
- package/cli/commands/generate.d.ts +4 -1
- package/cli/commands/generate.js +27 -8
- package/cli/introspect/transform-schema.d.ts +33 -21
- package/cli/introspect/transform-schema.js +31 -21
- package/esm/cli/codegen/barrel.d.ts +4 -1
- package/esm/cli/codegen/barrel.js +18 -12
- package/esm/cli/codegen/client.js +33 -0
- package/esm/cli/codegen/custom-mutations.d.ts +11 -1
- package/esm/cli/codegen/custom-mutations.js +50 -16
- package/esm/cli/codegen/custom-queries.d.ts +8 -0
- package/esm/cli/codegen/custom-queries.js +83 -48
- package/esm/cli/codegen/gql-ast.js +10 -6
- package/esm/cli/codegen/index.js +39 -8
- package/esm/cli/codegen/mutations.d.ts +14 -4
- package/esm/cli/codegen/mutations.js +115 -29
- package/esm/cli/codegen/orm/barrel.js +4 -2
- package/esm/cli/codegen/orm/index.js +17 -0
- package/esm/cli/codegen/orm/input-types-generator.js +83 -29
- package/esm/cli/codegen/orm/model-generator.js +7 -5
- package/esm/cli/codegen/queries.d.ts +7 -3
- package/esm/cli/codegen/queries.js +186 -159
- package/esm/cli/codegen/scalars.d.ts +6 -4
- package/esm/cli/codegen/scalars.js +16 -8
- package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
- package/esm/cli/codegen/schema-types-generator.js +362 -0
- package/esm/cli/codegen/ts-ast.d.ts +3 -1
- package/esm/cli/codegen/ts-ast.js +2 -2
- package/esm/cli/codegen/type-resolver.d.ts +52 -6
- package/esm/cli/codegen/type-resolver.js +97 -20
- package/esm/cli/codegen/types.d.ts +7 -4
- package/esm/cli/codegen/types.js +95 -41
- package/esm/cli/codegen/utils.d.ts +20 -2
- package/esm/cli/codegen/utils.js +31 -7
- package/esm/cli/commands/generate-orm.js +5 -5
- package/esm/cli/commands/generate.d.ts +4 -1
- package/esm/cli/commands/generate.js +27 -8
- package/esm/cli/introspect/transform-schema.d.ts +33 -21
- package/esm/cli/introspect/transform-schema.js +31 -21
- package/esm/types/config.d.ts +16 -1
- package/esm/types/config.js +6 -0
- package/esm/types/schema.d.ts +2 -0
- package/package.json +8 -6
- package/types/config.d.ts +16 -1
- package/types/config.js +6 -0
- package/types/schema.d.ts +2 -0
- package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
- package/__tests__/codegen/input-types-generator.test.js +0 -635
- package/cli/codegen/filters.d.ts +0 -27
- package/cli/codegen/filters.js +0 -357
- package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
- package/cli/codegen/orm/input-types-generator.test.js +0 -75
- package/cli/codegen/orm/select-types.test.d.ts +0 -11
- package/cli/codegen/orm/select-types.test.js +0 -22
- package/cli/introspect/transform-schema.test.d.ts +0 -1
- package/cli/introspect/transform-schema.test.js +0 -67
- package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
- package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
- package/esm/cli/codegen/filters.d.ts +0 -27
- package/esm/cli/codegen/filters.js +0 -351
- package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
- package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
- package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
- package/esm/cli/codegen/orm/select-types.test.js +0 -21
- package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
- package/esm/cli/introspect/transform-schema.test.js +0 -65
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, createTypeAlias, createUnionType, createFilterInterface, } from './ts-ast';
|
|
2
2
|
import { buildListQueryAST, buildSingleQueryAST, printGraphQL, } from './gql-ast';
|
|
3
|
-
import { getTableNames, getListQueryHookName, getSingleQueryHookName, getListQueryFileName, getSingleQueryFileName, getAllRowsQueryName, getSingleRowQueryName, getFilterTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, toScreamingSnake, ucFirst, } from './utils';
|
|
3
|
+
import { getTableNames, getListQueryHookName, getSingleQueryHookName, getListQueryFileName, getSingleQueryFileName, getAllRowsQueryName, getSingleRowQueryName, getFilterTypeName, getOrderByTypeName, getScalarFields, getScalarFilterType, getPrimaryKeyInfo, toScreamingSnake, ucFirst, } from './utils';
|
|
4
4
|
// ============================================================================
|
|
5
5
|
// List query hook generator
|
|
6
6
|
// ============================================================================
|
|
7
7
|
/**
|
|
8
8
|
* Generate list query hook file content using AST
|
|
9
9
|
*/
|
|
10
|
-
export function generateListQueryHook(table) {
|
|
10
|
+
export function generateListQueryHook(table, options = {}) {
|
|
11
|
+
const { reactQueryEnabled = true } = options;
|
|
11
12
|
const project = createProject();
|
|
12
13
|
const { typeName, pluralName } = getTableNames(table);
|
|
13
14
|
const hookName = getListQueryHookName(table);
|
|
@@ -20,32 +21,36 @@ export function generateListQueryHook(table) {
|
|
|
20
21
|
const queryDocument = printGraphQL(queryAST);
|
|
21
22
|
const sourceFile = createSourceFile(project, getListQueryFileName(table));
|
|
22
23
|
// Add file header as leading comment
|
|
23
|
-
|
|
24
|
+
const headerText = reactQueryEnabled
|
|
25
|
+
? `List query hook for ${typeName}`
|
|
26
|
+
: `List query functions for ${typeName}`;
|
|
27
|
+
sourceFile.insertText(0, createFileHeader(headerText) + '\n\n');
|
|
24
28
|
// Collect all filter types used by this table's fields
|
|
25
29
|
const filterTypesUsed = new Set();
|
|
26
30
|
for (const field of scalarFields) {
|
|
27
|
-
const filterType = getScalarFilterType(field.type.gqlType);
|
|
31
|
+
const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
|
|
28
32
|
if (filterType) {
|
|
29
33
|
filterTypesUsed.add(filterType);
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
|
-
// Add imports
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
// Add imports - conditionally include React Query imports
|
|
37
|
+
const imports = [];
|
|
38
|
+
if (reactQueryEnabled) {
|
|
39
|
+
imports.push(createImport({
|
|
35
40
|
moduleSpecifier: '@tanstack/react-query',
|
|
36
41
|
namedImports: ['useQuery'],
|
|
37
42
|
typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
imports.push(createImport({
|
|
46
|
+
moduleSpecifier: '../client',
|
|
47
|
+
namedImports: ['execute'],
|
|
48
|
+
typeOnlyNamedImports: ['ExecuteOptions'],
|
|
49
|
+
}), createImport({
|
|
50
|
+
moduleSpecifier: '../types',
|
|
51
|
+
typeOnlyNamedImports: [typeName, ...Array.from(filterTypesUsed)],
|
|
52
|
+
}));
|
|
53
|
+
sourceFile.addImportDeclarations(imports);
|
|
49
54
|
// Re-export entity type
|
|
50
55
|
sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
|
|
51
56
|
// Add section comment
|
|
@@ -61,12 +66,14 @@ export function generateListQueryHook(table) {
|
|
|
61
66
|
// Generate filter interface
|
|
62
67
|
const fieldFilters = scalarFields
|
|
63
68
|
.map((field) => {
|
|
64
|
-
const filterType = getScalarFilterType(field.type.gqlType);
|
|
69
|
+
const filterType = getScalarFilterType(field.type.gqlType, field.type.isArray);
|
|
65
70
|
return filterType ? { fieldName: field.name, filterType } : null;
|
|
66
71
|
})
|
|
67
72
|
.filter((f) => f !== null);
|
|
68
|
-
|
|
73
|
+
// Note: Not exported to avoid conflicts with schema-types
|
|
74
|
+
sourceFile.addInterface(createFilterInterface(filterTypeName, fieldFilters, { isExported: false }));
|
|
69
75
|
// Generate OrderBy type
|
|
76
|
+
// Note: Not exported to avoid conflicts with schema-types
|
|
70
77
|
const orderByValues = [
|
|
71
78
|
...scalarFields.flatMap((f) => [
|
|
72
79
|
`${toScreamingSnake(f.name)}_ASC`,
|
|
@@ -76,7 +83,7 @@ export function generateListQueryHook(table) {
|
|
|
76
83
|
'PRIMARY_KEY_ASC',
|
|
77
84
|
'PRIMARY_KEY_DESC',
|
|
78
85
|
];
|
|
79
|
-
sourceFile.addTypeAlias(createTypeAlias(orderByTypeName, createUnionType(orderByValues)));
|
|
86
|
+
sourceFile.addTypeAlias(createTypeAlias(orderByTypeName, createUnionType(orderByValues), { isExported: false }));
|
|
80
87
|
// Variables interface
|
|
81
88
|
const variablesProps = [
|
|
82
89
|
{ name: 'first', type: 'number', optional: true },
|
|
@@ -109,27 +116,28 @@ export function generateListQueryHook(table) {
|
|
|
109
116
|
// Query key factory
|
|
110
117
|
sourceFile.addVariableStatement(createConst(`${queryName}QueryKey`, `(variables?: ${ucFirst(pluralName)}QueryVariables) =>
|
|
111
118
|
['${typeName.toLowerCase()}', 'list', variables] as const`));
|
|
112
|
-
// Add section
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
119
|
+
// Add React Query hook section (only if enabled)
|
|
120
|
+
if (reactQueryEnabled) {
|
|
121
|
+
sourceFile.addStatements('\n// ============================================================================');
|
|
122
|
+
sourceFile.addStatements('// Hook');
|
|
123
|
+
sourceFile.addStatements('// ============================================================================\n');
|
|
124
|
+
// Hook function
|
|
125
|
+
sourceFile.addFunction({
|
|
126
|
+
name: hookName,
|
|
127
|
+
isExported: true,
|
|
128
|
+
parameters: [
|
|
129
|
+
{
|
|
130
|
+
name: 'variables',
|
|
131
|
+
type: `${ucFirst(pluralName)}QueryVariables`,
|
|
132
|
+
hasQuestionToken: true,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'options',
|
|
136
|
+
type: `Omit<UseQueryOptions<${ucFirst(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
|
|
137
|
+
hasQuestionToken: true,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
statements: `return useQuery({
|
|
133
141
|
queryKey: ${queryName}QueryKey(variables),
|
|
134
142
|
queryFn: () => execute<${ucFirst(pluralName)}QueryResult, ${ucFirst(pluralName)}QueryVariables>(
|
|
135
143
|
${queryName}QueryDocument,
|
|
@@ -137,9 +145,9 @@ export function generateListQueryHook(table) {
|
|
|
137
145
|
),
|
|
138
146
|
...options,
|
|
139
147
|
});`,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
docs: [
|
|
149
|
+
{
|
|
150
|
+
description: `Query hook for fetching ${typeName} list
|
|
143
151
|
|
|
144
152
|
@example
|
|
145
153
|
\`\`\`tsx
|
|
@@ -149,9 +157,10 @@ const { data, isLoading } = ${hookName}({
|
|
|
149
157
|
orderBy: ['CREATED_AT_DESC'],
|
|
150
158
|
});
|
|
151
159
|
\`\`\``,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
155
164
|
// Add section comment for standalone functions
|
|
156
165
|
sourceFile.addStatements('\n// ============================================================================');
|
|
157
166
|
sourceFile.addStatements('// Standalone Functions (non-React)');
|
|
@@ -197,29 +206,30 @@ const data = await queryClient.fetchQuery({
|
|
|
197
206
|
},
|
|
198
207
|
],
|
|
199
208
|
});
|
|
200
|
-
// Prefetch function (for SSR/QueryClient)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
209
|
+
// Prefetch function (for SSR/QueryClient) - only if React Query is enabled
|
|
210
|
+
if (reactQueryEnabled) {
|
|
211
|
+
sourceFile.addFunction({
|
|
212
|
+
name: `prefetch${ucFirst(pluralName)}Query`,
|
|
213
|
+
isExported: true,
|
|
214
|
+
isAsync: true,
|
|
215
|
+
parameters: [
|
|
216
|
+
{
|
|
217
|
+
name: 'queryClient',
|
|
218
|
+
type: 'QueryClient',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'variables',
|
|
222
|
+
type: `${ucFirst(pluralName)}QueryVariables`,
|
|
223
|
+
hasQuestionToken: true,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'options',
|
|
227
|
+
type: 'ExecuteOptions',
|
|
228
|
+
hasQuestionToken: true,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
returnType: 'Promise<void>',
|
|
232
|
+
statements: `await queryClient.prefetchQuery({
|
|
223
233
|
queryKey: ${queryName}QueryKey(variables),
|
|
224
234
|
queryFn: () => execute<${ucFirst(pluralName)}QueryResult, ${ucFirst(pluralName)}QueryVariables>(
|
|
225
235
|
${queryName}QueryDocument,
|
|
@@ -227,17 +237,18 @@ const data = await queryClient.fetchQuery({
|
|
|
227
237
|
options
|
|
228
238
|
),
|
|
229
239
|
});`,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
docs: [
|
|
241
|
+
{
|
|
242
|
+
description: `Prefetch ${typeName} list for SSR or cache warming
|
|
233
243
|
|
|
234
244
|
@example
|
|
235
245
|
\`\`\`ts
|
|
236
246
|
await prefetch${ucFirst(pluralName)}Query(queryClient, { first: 10 });
|
|
237
247
|
\`\`\``,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
});
|
|
251
|
+
}
|
|
241
252
|
return {
|
|
242
253
|
fileName: getListQueryFileName(table),
|
|
243
254
|
content: getFormattedOutput(sourceFile),
|
|
@@ -249,34 +260,46 @@ await prefetch${ucFirst(pluralName)}Query(queryClient, { first: 10 });
|
|
|
249
260
|
/**
|
|
250
261
|
* Generate single item query hook file content using AST
|
|
251
262
|
*/
|
|
252
|
-
export function generateSingleQueryHook(table) {
|
|
263
|
+
export function generateSingleQueryHook(table, options = {}) {
|
|
264
|
+
const { reactQueryEnabled = true } = options;
|
|
253
265
|
const project = createProject();
|
|
254
266
|
const { typeName, singularName } = getTableNames(table);
|
|
255
267
|
const hookName = getSingleQueryHookName(table);
|
|
256
268
|
const queryName = getSingleRowQueryName(table);
|
|
269
|
+
// Get primary key info dynamically from table constraints
|
|
270
|
+
const pkFields = getPrimaryKeyInfo(table);
|
|
271
|
+
// For simplicity, use first PK field (most common case)
|
|
272
|
+
// Composite PKs would need more complex handling
|
|
273
|
+
const pkField = pkFields[0];
|
|
274
|
+
const pkName = pkField.name;
|
|
275
|
+
const pkTsType = pkField.tsType;
|
|
257
276
|
// Generate GraphQL document via AST
|
|
258
277
|
const queryAST = buildSingleQueryAST({ table });
|
|
259
278
|
const queryDocument = printGraphQL(queryAST);
|
|
260
279
|
const sourceFile = createSourceFile(project, getSingleQueryFileName(table));
|
|
261
280
|
// Add file header
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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 = [];
|
|
287
|
+
if (reactQueryEnabled) {
|
|
288
|
+
imports.push(createImport({
|
|
266
289
|
moduleSpecifier: '@tanstack/react-query',
|
|
267
290
|
namedImports: ['useQuery'],
|
|
268
291
|
typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
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);
|
|
280
303
|
// Re-export entity type
|
|
281
304
|
sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
|
|
282
305
|
// Add section comment
|
|
@@ -289,9 +312,9 @@ export function generateSingleQueryHook(table) {
|
|
|
289
312
|
sourceFile.addStatements('\n// ============================================================================');
|
|
290
313
|
sourceFile.addStatements('// Types');
|
|
291
314
|
sourceFile.addStatements('// ============================================================================\n');
|
|
292
|
-
// Variables interface
|
|
315
|
+
// Variables interface - use dynamic PK field name and type
|
|
293
316
|
sourceFile.addInterface(createInterface(`${ucFirst(singularName)}QueryVariables`, [
|
|
294
|
-
{ name:
|
|
317
|
+
{ name: pkName, type: pkTsType },
|
|
295
318
|
]));
|
|
296
319
|
// Result interface
|
|
297
320
|
sourceFile.addInterface(createInterface(`${ucFirst(singularName)}QueryResult`, [
|
|
@@ -301,60 +324,62 @@ export function generateSingleQueryHook(table) {
|
|
|
301
324
|
sourceFile.addStatements('\n// ============================================================================');
|
|
302
325
|
sourceFile.addStatements('// Query Key');
|
|
303
326
|
sourceFile.addStatements('// ============================================================================\n');
|
|
304
|
-
// Query key factory
|
|
305
|
-
sourceFile.addVariableStatement(createConst(`${queryName}QueryKey`, `(
|
|
306
|
-
['${typeName.toLowerCase()}', 'detail',
|
|
307
|
-
// Add section
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
if (reactQueryEnabled) {
|
|
332
|
+
sourceFile.addStatements('\n// ============================================================================');
|
|
333
|
+
sourceFile.addStatements('// Hook');
|
|
334
|
+
sourceFile.addStatements('// ============================================================================\n');
|
|
335
|
+
// Hook function - use dynamic PK field name and type
|
|
336
|
+
sourceFile.addFunction({
|
|
337
|
+
name: hookName,
|
|
338
|
+
isExported: true,
|
|
339
|
+
parameters: [
|
|
340
|
+
{ name: pkName, type: pkTsType },
|
|
341
|
+
{
|
|
342
|
+
name: 'options',
|
|
343
|
+
type: `Omit<UseQueryOptions<${ucFirst(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
|
|
344
|
+
hasQuestionToken: true,
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
statements: `return useQuery({
|
|
348
|
+
queryKey: ${queryName}QueryKey(${pkName}),
|
|
325
349
|
queryFn: () => execute<${ucFirst(singularName)}QueryResult, ${ucFirst(singularName)}QueryVariables>(
|
|
326
350
|
${queryName}QueryDocument,
|
|
327
|
-
{
|
|
351
|
+
{ ${pkName} }
|
|
328
352
|
),
|
|
329
|
-
enabled:
|
|
353
|
+
enabled: !!${pkName} && (options?.enabled !== false),
|
|
330
354
|
...options,
|
|
331
355
|
});`,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
356
|
+
docs: [
|
|
357
|
+
{
|
|
358
|
+
description: `Query hook for fetching a single ${typeName} by primary key
|
|
335
359
|
|
|
336
360
|
@example
|
|
337
361
|
\`\`\`tsx
|
|
338
|
-
const { data, isLoading } = ${hookName}('
|
|
362
|
+
const { data, isLoading } = ${hookName}(${pkTsType === 'string' ? "'value-here'" : '123'});
|
|
339
363
|
|
|
340
364
|
if (data?.${queryName}) {
|
|
341
|
-
console.log(data.${queryName}
|
|
365
|
+
console.log(data.${queryName}.${pkName});
|
|
342
366
|
}
|
|
343
367
|
\`\`\``,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
});
|
|
371
|
+
}
|
|
347
372
|
// Add section comment for standalone functions
|
|
348
373
|
sourceFile.addStatements('\n// ============================================================================');
|
|
349
374
|
sourceFile.addStatements('// Standalone Functions (non-React)');
|
|
350
375
|
sourceFile.addStatements('// ============================================================================\n');
|
|
351
|
-
// Fetch function (standalone, no React)
|
|
376
|
+
// Fetch function (standalone, no React) - use dynamic PK
|
|
352
377
|
sourceFile.addFunction({
|
|
353
378
|
name: `fetch${ucFirst(singularName)}Query`,
|
|
354
379
|
isExported: true,
|
|
355
380
|
isAsync: true,
|
|
356
381
|
parameters: [
|
|
357
|
-
{ name:
|
|
382
|
+
{ name: pkName, type: pkTsType },
|
|
358
383
|
{
|
|
359
384
|
name: 'options',
|
|
360
385
|
type: 'ExecuteOptions',
|
|
@@ -364,54 +389,56 @@ if (data?.${queryName}) {
|
|
|
364
389
|
returnType: `Promise<${ucFirst(singularName)}QueryResult>`,
|
|
365
390
|
statements: `return execute<${ucFirst(singularName)}QueryResult, ${ucFirst(singularName)}QueryVariables>(
|
|
366
391
|
${queryName}QueryDocument,
|
|
367
|
-
{
|
|
392
|
+
{ ${pkName} },
|
|
368
393
|
options
|
|
369
394
|
);`,
|
|
370
395
|
docs: [
|
|
371
396
|
{
|
|
372
|
-
description: `Fetch a single ${typeName} by
|
|
397
|
+
description: `Fetch a single ${typeName} by primary key without React hooks
|
|
373
398
|
|
|
374
399
|
@example
|
|
375
400
|
\`\`\`ts
|
|
376
|
-
const data = await fetch${ucFirst(singularName)}Query('
|
|
401
|
+
const data = await fetch${ucFirst(singularName)}Query(${pkTsType === 'string' ? "'value-here'" : '123'});
|
|
377
402
|
\`\`\``,
|
|
378
403
|
},
|
|
379
404
|
],
|
|
380
405
|
});
|
|
381
|
-
// Prefetch function (for SSR/QueryClient)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
406
|
+
// Prefetch function (for SSR/QueryClient) - only if React Query is enabled, use dynamic PK
|
|
407
|
+
if (reactQueryEnabled) {
|
|
408
|
+
sourceFile.addFunction({
|
|
409
|
+
name: `prefetch${ucFirst(singularName)}Query`,
|
|
410
|
+
isExported: true,
|
|
411
|
+
isAsync: true,
|
|
412
|
+
parameters: [
|
|
413
|
+
{ name: 'queryClient', type: 'QueryClient' },
|
|
414
|
+
{ name: pkName, type: pkTsType },
|
|
415
|
+
{
|
|
416
|
+
name: 'options',
|
|
417
|
+
type: 'ExecuteOptions',
|
|
418
|
+
hasQuestionToken: true,
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
returnType: 'Promise<void>',
|
|
422
|
+
statements: `await queryClient.prefetchQuery({
|
|
423
|
+
queryKey: ${queryName}QueryKey(${pkName}),
|
|
398
424
|
queryFn: () => execute<${ucFirst(singularName)}QueryResult, ${ucFirst(singularName)}QueryVariables>(
|
|
399
425
|
${queryName}QueryDocument,
|
|
400
|
-
{
|
|
426
|
+
{ ${pkName} },
|
|
401
427
|
options
|
|
402
428
|
),
|
|
403
429
|
});`,
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
430
|
+
docs: [
|
|
431
|
+
{
|
|
432
|
+
description: `Prefetch a single ${typeName} for SSR or cache warming
|
|
407
433
|
|
|
408
434
|
@example
|
|
409
435
|
\`\`\`ts
|
|
410
|
-
await prefetch${ucFirst(singularName)}Query(queryClient, '
|
|
436
|
+
await prefetch${ucFirst(singularName)}Query(queryClient, ${pkTsType === 'string' ? "'value-here'" : '123'});
|
|
411
437
|
\`\`\``,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
});
|
|
441
|
+
}
|
|
415
442
|
return {
|
|
416
443
|
fileName: getSingleQueryFileName(table),
|
|
417
444
|
content: getFormattedOutput(sourceFile),
|
|
@@ -423,11 +450,11 @@ await prefetch${ucFirst(singularName)}Query(queryClient, 'uuid-here');
|
|
|
423
450
|
/**
|
|
424
451
|
* Generate all query hook files for all tables
|
|
425
452
|
*/
|
|
426
|
-
export function generateAllQueryHooks(tables) {
|
|
453
|
+
export function generateAllQueryHooks(tables, options = {}) {
|
|
427
454
|
const files = [];
|
|
428
455
|
for (const table of tables) {
|
|
429
|
-
files.push(generateListQueryHook(table));
|
|
430
|
-
files.push(generateSingleQueryHook(table));
|
|
456
|
+
files.push(generateListQueryHook(table, options));
|
|
457
|
+
files.push(generateSingleQueryHook(table, options));
|
|
431
458
|
}
|
|
432
459
|
return files;
|
|
433
460
|
}
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
export declare const SCALAR_TS_MAP: Record<string, string>;
|
|
5
5
|
export declare const SCALAR_FILTER_MAP: Record<string, string>;
|
|
6
6
|
export declare const SCALAR_NAMES: Set<string>;
|
|
7
|
-
|
|
7
|
+
/** All base filter type names - skip these in schema-types.ts to avoid duplicates */
|
|
8
|
+
export declare const BASE_FILTER_TYPE_NAMES: Set<string>;
|
|
9
|
+
export declare function scalarToTsType(scalarName: string, options?: {
|
|
8
10
|
unknownScalar?: 'unknown' | 'name';
|
|
9
11
|
overrides?: Record<string, string>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export declare function scalarToFilterType(scalarName: string): string | null;
|
|
12
|
+
}): string;
|
|
13
|
+
/** Get the filter type for a scalar (handles both scalar and array types) */
|
|
14
|
+
export declare function scalarToFilterType(scalarName: string, isArray?: boolean): string | null;
|
|
@@ -32,6 +32,8 @@ export const SCALAR_TS_MAP = {
|
|
|
32
32
|
MacAddr: 'string',
|
|
33
33
|
TsVector: 'string',
|
|
34
34
|
TsQuery: 'string',
|
|
35
|
+
// File upload
|
|
36
|
+
Upload: 'File',
|
|
35
37
|
};
|
|
36
38
|
export const SCALAR_FILTER_MAP = {
|
|
37
39
|
String: 'StringFilter',
|
|
@@ -52,15 +54,21 @@ export const SCALAR_FILTER_MAP = {
|
|
|
52
54
|
Interval: 'StringFilter',
|
|
53
55
|
};
|
|
54
56
|
export const SCALAR_NAMES = new Set(Object.keys(SCALAR_TS_MAP));
|
|
57
|
+
/** Scalars that have list filter variants (e.g., StringListFilter) */
|
|
58
|
+
const LIST_FILTER_SCALARS = new Set(['String', 'Int', 'UUID']);
|
|
59
|
+
/** All base filter type names - skip these in schema-types.ts to avoid duplicates */
|
|
60
|
+
export const BASE_FILTER_TYPE_NAMES = new Set([
|
|
61
|
+
...new Set(Object.values(SCALAR_FILTER_MAP)),
|
|
62
|
+
...Array.from(LIST_FILTER_SCALARS).map((s) => `${s}ListFilter`),
|
|
63
|
+
]);
|
|
55
64
|
export function scalarToTsType(scalarName, options = {}) {
|
|
56
|
-
|
|
57
|
-
if (override)
|
|
58
|
-
return override;
|
|
59
|
-
const mapped = SCALAR_TS_MAP[scalarName];
|
|
60
|
-
if (mapped)
|
|
61
|
-
return mapped;
|
|
62
|
-
return options.unknownScalar === 'unknown' ? 'unknown' : scalarName;
|
|
65
|
+
return options.overrides?.[scalarName] ?? SCALAR_TS_MAP[scalarName] ?? (options.unknownScalar === 'unknown' ? 'unknown' : scalarName);
|
|
63
66
|
}
|
|
64
|
-
|
|
67
|
+
/** Get the filter type for a scalar (handles both scalar and array types) */
|
|
68
|
+
export function scalarToFilterType(scalarName, isArray = false) {
|
|
69
|
+
const baseName = scalarName === 'ID' ? 'UUID' : scalarName;
|
|
70
|
+
if (isArray) {
|
|
71
|
+
return LIST_FILTER_SCALARS.has(baseName) ? `${baseName}ListFilter` : null;
|
|
72
|
+
}
|
|
65
73
|
return SCALAR_FILTER_MAP[scalarName] ?? null;
|
|
66
74
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TypeRegistry } from '../../types/schema';
|
|
2
|
+
export interface GeneratedSchemaTypesFile {
|
|
3
|
+
fileName: string;
|
|
4
|
+
content: string;
|
|
5
|
+
/** List of enum type names that were generated */
|
|
6
|
+
generatedEnums: string[];
|
|
7
|
+
/** List of table entity types that are referenced */
|
|
8
|
+
referencedTableTypes: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface GenerateSchemaTypesOptions {
|
|
11
|
+
/** The TypeRegistry containing all GraphQL types */
|
|
12
|
+
typeRegistry: TypeRegistry;
|
|
13
|
+
/** Type names that already exist in types.ts (table entity types) */
|
|
14
|
+
tableTypeNames: Set<string>;
|
|
15
|
+
}
|
|
16
|
+
export interface PayloadTypesResult {
|
|
17
|
+
generatedTypes: Set<string>;
|
|
18
|
+
referencedTableTypes: Set<string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generate comprehensive schema-types.ts file using ts-morph AST
|
|
22
|
+
*
|
|
23
|
+
* This generates all Input/Payload/Enum types from the TypeRegistry
|
|
24
|
+
* that are needed by custom mutation/query hooks.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generateSchemaTypesFile(options: GenerateSchemaTypesOptions): GeneratedSchemaTypesFile;
|