@constructive-io/graphql-codegen 2.21.0 → 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/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 +4 -0
- package/cli/codegen/custom-mutations.js +39 -13
- package/cli/codegen/custom-queries.d.ts +4 -0
- package/cli/codegen/custom-queries.js +36 -11
- package/cli/codegen/gql-ast.js +9 -5
- package/cli/codegen/index.js +35 -7
- package/cli/codegen/mutations.d.ts +2 -0
- package/cli/codegen/mutations.js +87 -23
- 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.js +36 -27
- 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 +4 -0
- package/esm/cli/codegen/custom-mutations.js +40 -14
- package/esm/cli/codegen/custom-queries.d.ts +4 -0
- package/esm/cli/codegen/custom-queries.js +37 -12
- package/esm/cli/codegen/gql-ast.js +10 -6
- package/esm/cli/codegen/index.js +35 -7
- package/esm/cli/codegen/mutations.d.ts +2 -0
- package/esm/cli/codegen/mutations.js +88 -24
- 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.js +37 -28
- 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/schema.d.ts +2 -0
- package/package.json +8 -6
- 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/__tests__/codegen/react-query-optional.test.d.ts +0 -1
- package/__tests__/codegen/react-query-optional.test.js +0 -292
- 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/__tests__/codegen/react-query-optional.test.d.ts +0 -1
- package/esm/__tests__/codegen/react-query-optional.test.js +0 -290
- 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
package/cli/commands/generate.js
CHANGED
|
@@ -149,17 +149,17 @@ async function generateCommand(options = {}) {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
// 7. Generate code
|
|
152
|
-
log('Generating code...');
|
|
152
|
+
console.log('Generating code...');
|
|
153
153
|
const { files: generatedFiles, stats } = (0, codegen_1.generate)({
|
|
154
154
|
tables,
|
|
155
155
|
customOperations: customOperationsData,
|
|
156
156
|
config,
|
|
157
157
|
});
|
|
158
|
-
log(`
|
|
159
|
-
log(`
|
|
160
|
-
log(`
|
|
161
|
-
log(`
|
|
162
|
-
log(`
|
|
158
|
+
console.log(`Generated ${stats.totalFiles} files`);
|
|
159
|
+
log(` ${stats.queryHooks} table query hooks`);
|
|
160
|
+
log(` ${stats.mutationHooks} table mutation hooks`);
|
|
161
|
+
log(` ${stats.customQueryHooks} custom query hooks`);
|
|
162
|
+
log(` ${stats.customMutationHooks} custom mutation hooks`);
|
|
163
163
|
if (options.dryRun) {
|
|
164
164
|
return {
|
|
165
165
|
success: true,
|
|
@@ -228,9 +228,12 @@ async function loadConfig(options) {
|
|
|
228
228
|
const config = (0, config_1.resolveConfig)(mergedConfig);
|
|
229
229
|
return { success: true, config };
|
|
230
230
|
}
|
|
231
|
-
async function writeGeneratedFiles(files, outputDir, subdirs) {
|
|
231
|
+
async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
|
|
232
|
+
const { showProgress = true } = options;
|
|
232
233
|
const errors = [];
|
|
233
234
|
const written = [];
|
|
235
|
+
const total = files.length;
|
|
236
|
+
const isTTY = process.stdout.isTTY;
|
|
234
237
|
// Ensure output directory exists
|
|
235
238
|
try {
|
|
236
239
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -256,8 +259,20 @@ async function writeGeneratedFiles(files, outputDir, subdirs) {
|
|
|
256
259
|
if (errors.length > 0) {
|
|
257
260
|
return { success: false, errors };
|
|
258
261
|
}
|
|
259
|
-
for (
|
|
262
|
+
for (let i = 0; i < files.length; i++) {
|
|
263
|
+
const file = files[i];
|
|
260
264
|
const filePath = path.join(outputDir, file.path);
|
|
265
|
+
// Show progress
|
|
266
|
+
if (showProgress) {
|
|
267
|
+
const progress = Math.round(((i + 1) / total) * 100);
|
|
268
|
+
if (isTTY) {
|
|
269
|
+
process.stdout.write(`\rWriting files: ${i + 1}/${total} (${progress}%)`);
|
|
270
|
+
}
|
|
271
|
+
else if (i % 100 === 0 || i === total - 1) {
|
|
272
|
+
// Non-TTY: periodic updates for CI/CD
|
|
273
|
+
console.log(`Writing files: ${i + 1}/${total}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
261
276
|
// Ensure parent directory exists
|
|
262
277
|
const parentDir = path.dirname(filePath);
|
|
263
278
|
try {
|
|
@@ -277,6 +292,10 @@ async function writeGeneratedFiles(files, outputDir, subdirs) {
|
|
|
277
292
|
errors.push(`Failed to write ${filePath}: ${message}`);
|
|
278
293
|
}
|
|
279
294
|
}
|
|
295
|
+
// Clear progress line
|
|
296
|
+
if (showProgress && isTTY) {
|
|
297
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
298
|
+
}
|
|
280
299
|
return {
|
|
281
300
|
success: errors.length === 0,
|
|
282
301
|
filesWritten: written,
|
|
@@ -30,13 +30,29 @@ export declare function transformSchemaToOperations(response: IntrospectionQuery
|
|
|
30
30
|
* Uses glob-like patterns (supports * wildcard)
|
|
31
31
|
*/
|
|
32
32
|
export declare function filterOperations(operations: CleanOperation[], include?: string[], exclude?: string[]): CleanOperation[];
|
|
33
|
+
/**
|
|
34
|
+
* Result type for table operation names lookup
|
|
35
|
+
*/
|
|
36
|
+
export interface TableOperationNames {
|
|
37
|
+
queries: Set<string>;
|
|
38
|
+
mutations: Set<string>;
|
|
39
|
+
}
|
|
33
40
|
/**
|
|
34
41
|
* Get the set of table-related operation names from tables
|
|
35
42
|
* Used to identify which operations are already covered by table generators
|
|
36
43
|
*
|
|
37
|
-
* This
|
|
38
|
-
*
|
|
39
|
-
*
|
|
44
|
+
* IMPORTANT: This uses EXACT matches only from _meta.query fields.
|
|
45
|
+
* Any operation not explicitly listed in _meta will flow through as a
|
|
46
|
+
* custom operation, ensuring 100% coverage of the schema.
|
|
47
|
+
*
|
|
48
|
+
* Table operations (generated by table generators):
|
|
49
|
+
* - Queries: all (list), one (single by PK)
|
|
50
|
+
* - Mutations: create, update (by PK), delete (by PK)
|
|
51
|
+
*
|
|
52
|
+
* Custom operations (generated by custom operation generators):
|
|
53
|
+
* - Unique constraint lookups: *ByUsername, *ByEmail, etc.
|
|
54
|
+
* - Unique constraint mutations: update*By*, delete*By*
|
|
55
|
+
* - True custom operations: login, register, bootstrapUser, etc.
|
|
40
56
|
*/
|
|
41
57
|
export declare function getTableOperationNames(tables: Array<{
|
|
42
58
|
name: string;
|
|
@@ -47,28 +63,24 @@ export declare function getTableOperationNames(tables: Array<{
|
|
|
47
63
|
update: string | null;
|
|
48
64
|
delete: string | null;
|
|
49
65
|
};
|
|
50
|
-
|
|
51
|
-
tableType: string;
|
|
52
|
-
};
|
|
53
|
-
}>): {
|
|
54
|
-
queries: Set<string>;
|
|
55
|
-
mutations: Set<string>;
|
|
56
|
-
tableTypePatterns: RegExp[];
|
|
57
|
-
};
|
|
66
|
+
}>): TableOperationNames;
|
|
58
67
|
/**
|
|
59
68
|
* Check if an operation is a table operation (already handled by table generators)
|
|
69
|
+
*
|
|
70
|
+
* Uses EXACT match only - no pattern matching. This ensures:
|
|
71
|
+
* 1. Only operations explicitly in _meta.query are treated as table operations
|
|
72
|
+
* 2. All other operations (including update*By*, delete*By*) become custom operations
|
|
73
|
+
* 3. 100% schema coverage is guaranteed
|
|
60
74
|
*/
|
|
61
|
-
export declare function isTableOperation(operation: CleanOperation, tableOperationNames:
|
|
62
|
-
queries: Set<string>;
|
|
63
|
-
mutations: Set<string>;
|
|
64
|
-
tableTypePatterns: RegExp[];
|
|
65
|
-
}): boolean;
|
|
75
|
+
export declare function isTableOperation(operation: CleanOperation, tableOperationNames: TableOperationNames): boolean;
|
|
66
76
|
/**
|
|
67
77
|
* Get only custom operations (not covered by table generators)
|
|
78
|
+
*
|
|
79
|
+
* This returns ALL operations that are not exact matches for table CRUD operations.
|
|
80
|
+
* Includes:
|
|
81
|
+
* - Unique constraint queries (*ByUsername, *ByEmail, etc.)
|
|
82
|
+
* - Unique constraint mutations (update*By*, delete*By*)
|
|
83
|
+
* - True custom operations (login, register, bootstrapUser, etc.)
|
|
68
84
|
*/
|
|
69
|
-
export declare function getCustomOperations(operations: CleanOperation[], tableOperationNames:
|
|
70
|
-
queries: Set<string>;
|
|
71
|
-
mutations: Set<string>;
|
|
72
|
-
tableTypePatterns: RegExp[];
|
|
73
|
-
}): CleanOperation[];
|
|
85
|
+
export declare function getCustomOperations(operations: CleanOperation[], tableOperationNames: TableOperationNames): CleanOperation[];
|
|
74
86
|
export { unwrapType, getBaseTypeName, isNonNull };
|
|
@@ -38,6 +38,10 @@ function buildTypeRegistry(types) {
|
|
|
38
38
|
if (type.kind === 'ENUM' && type.enumValues) {
|
|
39
39
|
resolvedType.enumValues = type.enumValues.map((ev) => ev.name);
|
|
40
40
|
}
|
|
41
|
+
// Resolve possible types for UNION types (no circular refs for names)
|
|
42
|
+
if (type.kind === 'UNION' && type.possibleTypes) {
|
|
43
|
+
resolvedType.possibleTypes = type.possibleTypes.map((pt) => pt.name);
|
|
44
|
+
}
|
|
41
45
|
registry.set(type.name, resolvedType);
|
|
42
46
|
}
|
|
43
47
|
// Second pass: Resolve fields (now that all types exist in registry)
|
|
@@ -212,57 +216,63 @@ function matchesPatterns(name, patterns) {
|
|
|
212
216
|
return name === pattern;
|
|
213
217
|
});
|
|
214
218
|
}
|
|
215
|
-
// ============================================================================
|
|
216
|
-
// Utility Functions
|
|
217
|
-
// ============================================================================
|
|
218
219
|
/**
|
|
219
220
|
* Get the set of table-related operation names from tables
|
|
220
221
|
* Used to identify which operations are already covered by table generators
|
|
221
222
|
*
|
|
222
|
-
* This
|
|
223
|
-
*
|
|
224
|
-
*
|
|
223
|
+
* IMPORTANT: This uses EXACT matches only from _meta.query fields.
|
|
224
|
+
* Any operation not explicitly listed in _meta will flow through as a
|
|
225
|
+
* custom operation, ensuring 100% coverage of the schema.
|
|
226
|
+
*
|
|
227
|
+
* Table operations (generated by table generators):
|
|
228
|
+
* - Queries: all (list), one (single by PK)
|
|
229
|
+
* - Mutations: create, update (by PK), delete (by PK)
|
|
230
|
+
*
|
|
231
|
+
* Custom operations (generated by custom operation generators):
|
|
232
|
+
* - Unique constraint lookups: *ByUsername, *ByEmail, etc.
|
|
233
|
+
* - Unique constraint mutations: update*By*, delete*By*
|
|
234
|
+
* - True custom operations: login, register, bootstrapUser, etc.
|
|
225
235
|
*/
|
|
226
236
|
function getTableOperationNames(tables) {
|
|
227
237
|
const queries = new Set();
|
|
228
238
|
const mutations = new Set();
|
|
229
|
-
const tableTypePatterns = [];
|
|
230
239
|
for (const table of tables) {
|
|
231
240
|
if (table.query) {
|
|
241
|
+
// Add exact query names from _meta
|
|
232
242
|
queries.add(table.query.all);
|
|
233
243
|
queries.add(table.query.one);
|
|
244
|
+
// Add exact mutation names from _meta
|
|
234
245
|
mutations.add(table.query.create);
|
|
235
246
|
if (table.query.update)
|
|
236
247
|
mutations.add(table.query.update);
|
|
237
248
|
if (table.query.delete)
|
|
238
249
|
mutations.add(table.query.delete);
|
|
239
250
|
}
|
|
240
|
-
// Create patterns to match alternate CRUD mutations (updateXByY, deleteXByY)
|
|
241
|
-
if (table.inflection?.tableType) {
|
|
242
|
-
const typeName = table.inflection.tableType;
|
|
243
|
-
// Match: update{TypeName}By*, delete{TypeName}By*
|
|
244
|
-
tableTypePatterns.push(new RegExp(`^update${typeName}By`, 'i'));
|
|
245
|
-
tableTypePatterns.push(new RegExp(`^delete${typeName}By`, 'i'));
|
|
246
|
-
}
|
|
247
251
|
}
|
|
248
|
-
return { queries, mutations
|
|
252
|
+
return { queries, mutations };
|
|
249
253
|
}
|
|
250
254
|
/**
|
|
251
255
|
* Check if an operation is a table operation (already handled by table generators)
|
|
256
|
+
*
|
|
257
|
+
* Uses EXACT match only - no pattern matching. This ensures:
|
|
258
|
+
* 1. Only operations explicitly in _meta.query are treated as table operations
|
|
259
|
+
* 2. All other operations (including update*By*, delete*By*) become custom operations
|
|
260
|
+
* 3. 100% schema coverage is guaranteed
|
|
252
261
|
*/
|
|
253
262
|
function isTableOperation(operation, tableOperationNames) {
|
|
254
263
|
if (operation.kind === 'query') {
|
|
255
264
|
return tableOperationNames.queries.has(operation.name);
|
|
256
265
|
}
|
|
257
|
-
|
|
258
|
-
if (tableOperationNames.mutations.has(operation.name)) {
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
// Check pattern match for alternate CRUD mutations (updateXByY, deleteXByY)
|
|
262
|
-
return tableOperationNames.tableTypePatterns.some((pattern) => pattern.test(operation.name));
|
|
266
|
+
return tableOperationNames.mutations.has(operation.name);
|
|
263
267
|
}
|
|
264
268
|
/**
|
|
265
269
|
* Get only custom operations (not covered by table generators)
|
|
270
|
+
*
|
|
271
|
+
* This returns ALL operations that are not exact matches for table CRUD operations.
|
|
272
|
+
* Includes:
|
|
273
|
+
* - Unique constraint queries (*ByUsername, *ByEmail, etc.)
|
|
274
|
+
* - Unique constraint mutations (update*By*, delete*By*)
|
|
275
|
+
* - True custom operations (login, register, bootstrapUser, etc.)
|
|
266
276
|
*/
|
|
267
277
|
function getCustomOperations(operations, tableOperationNames) {
|
|
268
278
|
return operations.filter((op) => !isTableOperation(op, tableOperationNames));
|
|
@@ -15,8 +15,11 @@ export declare function generateQueriesBarrel(tables: CleanTable[]): string;
|
|
|
15
15
|
export declare function generateMutationsBarrel(tables: CleanTable[]): string;
|
|
16
16
|
/**
|
|
17
17
|
* Generate the main index.ts barrel file
|
|
18
|
+
*
|
|
19
|
+
* @param tables - The tables to include in the SDK
|
|
20
|
+
* @param hasSchemaTypes - Whether schema-types.ts was generated
|
|
18
21
|
*/
|
|
19
|
-
export declare function generateMainBarrel(tables: CleanTable[]): string;
|
|
22
|
+
export declare function generateMainBarrel(tables: CleanTable[], hasSchemaTypes?: boolean): string;
|
|
20
23
|
/**
|
|
21
24
|
* Generate queries barrel including custom query operations
|
|
22
25
|
*/
|
|
@@ -5,10 +5,7 @@ import { getOperationHookName } from './type-resolver';
|
|
|
5
5
|
* Generate the queries/index.ts barrel file
|
|
6
6
|
*/
|
|
7
7
|
export function generateQueriesBarrel(tables) {
|
|
8
|
-
const lines = [
|
|
9
|
-
createFileHeader('Query hooks barrel export'),
|
|
10
|
-
'',
|
|
11
|
-
];
|
|
8
|
+
const lines = [createFileHeader('Query hooks barrel export'), ''];
|
|
12
9
|
// Export all query hooks
|
|
13
10
|
for (const table of tables) {
|
|
14
11
|
const listHookName = getListQueryHookName(table);
|
|
@@ -44,31 +41,40 @@ export function generateMutationsBarrel(tables) {
|
|
|
44
41
|
}
|
|
45
42
|
/**
|
|
46
43
|
* Generate the main index.ts barrel file
|
|
44
|
+
*
|
|
45
|
+
* @param tables - The tables to include in the SDK
|
|
46
|
+
* @param hasSchemaTypes - Whether schema-types.ts was generated
|
|
47
47
|
*/
|
|
48
|
-
export function generateMainBarrel(tables) {
|
|
48
|
+
export function generateMainBarrel(tables, hasSchemaTypes = false) {
|
|
49
49
|
const tableNames = tables.map((t) => t.name).join(', ');
|
|
50
|
+
const schemaTypesExport = hasSchemaTypes
|
|
51
|
+
? `
|
|
52
|
+
// Schema types (input, payload, enum types)
|
|
53
|
+
export * from './schema-types';
|
|
54
|
+
`
|
|
55
|
+
: '';
|
|
50
56
|
return `/**
|
|
51
57
|
* Auto-generated GraphQL SDK
|
|
52
58
|
* @generated by @constructive-io/graphql-codegen
|
|
53
|
-
*
|
|
59
|
+
*
|
|
54
60
|
* Tables: ${tableNames}
|
|
55
|
-
*
|
|
61
|
+
*
|
|
56
62
|
* Usage:
|
|
57
|
-
*
|
|
63
|
+
*
|
|
58
64
|
* 1. Configure the client:
|
|
59
65
|
* \`\`\`ts
|
|
60
66
|
* import { configure } from './generated';
|
|
61
|
-
*
|
|
67
|
+
*
|
|
62
68
|
* configure({
|
|
63
69
|
* endpoint: 'https://api.example.com/graphql',
|
|
64
70
|
* headers: { Authorization: 'Bearer <token>' },
|
|
65
71
|
* });
|
|
66
72
|
* \`\`\`
|
|
67
|
-
*
|
|
73
|
+
*
|
|
68
74
|
* 2. Use the hooks:
|
|
69
75
|
* \`\`\`tsx
|
|
70
76
|
* import { useCarsQuery, useCreateCarMutation } from './generated';
|
|
71
|
-
*
|
|
77
|
+
*
|
|
72
78
|
* function MyComponent() {
|
|
73
79
|
* const { data, isLoading } = useCarsQuery({ first: 10 });
|
|
74
80
|
* const { mutate } = useCreateCarMutation();
|
|
@@ -82,7 +88,7 @@ export * from './client';
|
|
|
82
88
|
|
|
83
89
|
// Entity and filter types
|
|
84
90
|
export * from './types';
|
|
85
|
-
|
|
91
|
+
${schemaTypesExport}
|
|
86
92
|
// Query hooks
|
|
87
93
|
export * from './queries';
|
|
88
94
|
|
|
@@ -53,6 +53,39 @@ export function getConfig(): GraphQLClientConfig {
|
|
|
53
53
|
return globalConfig;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Set a single header value
|
|
58
|
+
* Useful for updating Authorization after login
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* \`\`\`ts
|
|
62
|
+
* setHeader('Authorization', 'Bearer <new-token>');
|
|
63
|
+
* \`\`\`
|
|
64
|
+
*/
|
|
65
|
+
export function setHeader(key: string, value: string): void {
|
|
66
|
+
const config = getConfig();
|
|
67
|
+
globalConfig = {
|
|
68
|
+
...config,
|
|
69
|
+
headers: { ...config.headers, [key]: value },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Merge multiple headers into the current configuration
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* \`\`\`ts
|
|
78
|
+
* setHeaders({ Authorization: 'Bearer <token>', 'X-Custom': 'value' });
|
|
79
|
+
* \`\`\`
|
|
80
|
+
*/
|
|
81
|
+
export function setHeaders(headers: Record<string, string>): void {
|
|
82
|
+
const config = getConfig();
|
|
83
|
+
globalConfig = {
|
|
84
|
+
...config,
|
|
85
|
+
headers: { ...config.headers, ...headers },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
// ============================================================================
|
|
57
90
|
// Error handling
|
|
58
91
|
// ============================================================================
|
|
@@ -23,6 +23,8 @@ export interface GenerateCustomMutationHookOptions {
|
|
|
23
23
|
skipQueryField?: boolean;
|
|
24
24
|
/** Whether to generate React Query hooks (default: true for backwards compatibility) */
|
|
25
25
|
reactQueryEnabled?: boolean;
|
|
26
|
+
/** Table entity type names (for import path resolution) */
|
|
27
|
+
tableTypeNames?: Set<string>;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* Generate a custom mutation hook file
|
|
@@ -36,6 +38,8 @@ export interface GenerateAllCustomMutationHooksOptions {
|
|
|
36
38
|
skipQueryField?: boolean;
|
|
37
39
|
/** Whether to generate React Query hooks (default: true for backwards compatibility) */
|
|
38
40
|
reactQueryEnabled?: boolean;
|
|
41
|
+
/** Table entity type names (for import path resolution) */
|
|
42
|
+
tableTypeNames?: Set<string>;
|
|
39
43
|
}
|
|
40
44
|
/**
|
|
41
45
|
* Generate all custom mutation hook files
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
|
|
2
2
|
import { buildCustomMutationString } from './schema-gql-ast';
|
|
3
|
-
import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, } from './type-resolver';
|
|
3
|
+
import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, createTypeTracker, } from './type-resolver';
|
|
4
4
|
/**
|
|
5
5
|
* Generate a custom mutation hook file
|
|
6
6
|
* When reactQueryEnabled is false, returns null since mutations require React Query
|
|
@@ -21,13 +21,15 @@ export function generateCustomMutationHook(options) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
function generateCustomMutationHookInternal(options) {
|
|
24
|
-
const { operation, typeRegistry, maxDepth = 2, skipQueryField = true } = options;
|
|
24
|
+
const { operation, typeRegistry, maxDepth = 2, skipQueryField = true, tableTypeNames } = options;
|
|
25
25
|
const project = createProject();
|
|
26
26
|
const hookName = getOperationHookName(operation.name, 'mutation');
|
|
27
27
|
const fileName = getOperationFileName(operation.name, 'mutation');
|
|
28
28
|
const variablesTypeName = getOperationVariablesTypeName(operation.name, 'mutation');
|
|
29
29
|
const resultTypeName = getOperationResultTypeName(operation.name, 'mutation');
|
|
30
30
|
const documentConstName = getDocumentConstName(operation.name, 'mutation');
|
|
31
|
+
// Create type tracker to collect referenced types (with table type awareness)
|
|
32
|
+
const tracker = createTypeTracker({ tableTypeNames });
|
|
31
33
|
// Generate GraphQL document
|
|
32
34
|
const mutationDocument = buildCustomMutationString({
|
|
33
35
|
operation,
|
|
@@ -38,8 +40,21 @@ function generateCustomMutationHookInternal(options) {
|
|
|
38
40
|
const sourceFile = createSourceFile(project, fileName);
|
|
39
41
|
// Add file header
|
|
40
42
|
sourceFile.insertText(0, createFileHeader(`Custom mutation hook for ${operation.name}`) + '\n\n');
|
|
43
|
+
// Generate variables interface if there are arguments (with tracking)
|
|
44
|
+
let variablesProps = [];
|
|
45
|
+
if (operation.args.length > 0) {
|
|
46
|
+
variablesProps = generateVariablesProperties(operation.args, tracker);
|
|
47
|
+
}
|
|
48
|
+
// Generate result interface (with tracking)
|
|
49
|
+
const resultType = typeRefToTsType(operation.returnType, tracker);
|
|
50
|
+
const resultProps = [
|
|
51
|
+
{ name: operation.name, type: resultType },
|
|
52
|
+
];
|
|
53
|
+
// Get importable types from tracker (separated by source)
|
|
54
|
+
const schemaTypes = tracker.getImportableTypes(); // From schema-types.ts
|
|
55
|
+
const tableTypes = tracker.getTableTypes(); // From types.ts
|
|
41
56
|
// Add imports
|
|
42
|
-
|
|
57
|
+
const imports = [
|
|
43
58
|
createImport({
|
|
44
59
|
moduleSpecifier: '@tanstack/react-query',
|
|
45
60
|
namedImports: ['useMutation'],
|
|
@@ -49,21 +64,31 @@ function generateCustomMutationHookInternal(options) {
|
|
|
49
64
|
moduleSpecifier: '../client',
|
|
50
65
|
namedImports: ['execute'],
|
|
51
66
|
}),
|
|
52
|
-
]
|
|
67
|
+
];
|
|
68
|
+
// Add types.ts import for table entity types
|
|
69
|
+
if (tableTypes.length > 0) {
|
|
70
|
+
imports.push(createImport({
|
|
71
|
+
moduleSpecifier: '../types',
|
|
72
|
+
typeOnlyNamedImports: tableTypes,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
// Add schema-types import for Input/Payload/Enum types
|
|
76
|
+
if (schemaTypes.length > 0) {
|
|
77
|
+
imports.push(createImport({
|
|
78
|
+
moduleSpecifier: '../schema-types',
|
|
79
|
+
typeOnlyNamedImports: schemaTypes,
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
sourceFile.addImportDeclarations(imports);
|
|
53
83
|
// Add mutation document constant
|
|
54
84
|
sourceFile.addVariableStatement(createConst(documentConstName, '`\n' + mutationDocument + '`', {
|
|
55
85
|
docs: ['GraphQL mutation document'],
|
|
56
86
|
}));
|
|
57
|
-
//
|
|
87
|
+
// Add variables interface
|
|
58
88
|
if (operation.args.length > 0) {
|
|
59
|
-
const variablesProps = generateVariablesProperties(operation.args);
|
|
60
89
|
sourceFile.addInterface(createInterface(variablesTypeName, variablesProps));
|
|
61
90
|
}
|
|
62
|
-
//
|
|
63
|
-
const resultType = typeRefToTsType(operation.returnType);
|
|
64
|
-
const resultProps = [
|
|
65
|
-
{ name: operation.name, type: resultType },
|
|
66
|
-
];
|
|
91
|
+
// Add result interface
|
|
67
92
|
sourceFile.addInterface(createInterface(resultTypeName, resultProps));
|
|
68
93
|
// Generate hook function
|
|
69
94
|
const hookParams = generateHookParameters(operation, variablesTypeName, resultTypeName);
|
|
@@ -87,10 +112,10 @@ function generateCustomMutationHookInternal(options) {
|
|
|
87
112
|
/**
|
|
88
113
|
* Generate interface properties from CleanArguments
|
|
89
114
|
*/
|
|
90
|
-
function generateVariablesProperties(args) {
|
|
115
|
+
function generateVariablesProperties(args, tracker) {
|
|
91
116
|
return args.map((arg) => ({
|
|
92
117
|
name: arg.name,
|
|
93
|
-
type: typeRefToTsType(arg.type),
|
|
118
|
+
type: typeRefToTsType(arg.type, tracker),
|
|
94
119
|
optional: !isTypeRequired(arg.type),
|
|
95
120
|
docs: arg.description ? [arg.description] : undefined,
|
|
96
121
|
}));
|
|
@@ -139,7 +164,7 @@ function generateHookBody(operation, documentConstName, variablesTypeName, resul
|
|
|
139
164
|
* When reactQueryEnabled is false, returns empty array since mutations require React Query
|
|
140
165
|
*/
|
|
141
166
|
export function generateAllCustomMutationHooks(options) {
|
|
142
|
-
const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true } = options;
|
|
167
|
+
const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
|
|
143
168
|
return operations
|
|
144
169
|
.filter((op) => op.kind === 'mutation')
|
|
145
170
|
.map((operation) => generateCustomMutationHook({
|
|
@@ -148,6 +173,7 @@ export function generateAllCustomMutationHooks(options) {
|
|
|
148
173
|
maxDepth,
|
|
149
174
|
skipQueryField,
|
|
150
175
|
reactQueryEnabled,
|
|
176
|
+
tableTypeNames,
|
|
151
177
|
}))
|
|
152
178
|
.filter((result) => result !== null);
|
|
153
179
|
}
|
|
@@ -23,6 +23,8 @@ export interface GenerateCustomQueryHookOptions {
|
|
|
23
23
|
skipQueryField?: boolean;
|
|
24
24
|
/** Whether to generate React Query hooks (default: true for backwards compatibility) */
|
|
25
25
|
reactQueryEnabled?: boolean;
|
|
26
|
+
/** Table entity type names (for import path resolution) */
|
|
27
|
+
tableTypeNames?: Set<string>;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* Generate a custom query hook file
|
|
@@ -35,6 +37,8 @@ export interface GenerateAllCustomQueryHooksOptions {
|
|
|
35
37
|
skipQueryField?: boolean;
|
|
36
38
|
/** Whether to generate React Query hooks (default: true for backwards compatibility) */
|
|
37
39
|
reactQueryEnabled?: boolean;
|
|
40
|
+
/** Table entity type names (for import path resolution) */
|
|
41
|
+
tableTypeNames?: Set<string>;
|
|
38
42
|
}
|
|
39
43
|
/**
|
|
40
44
|
* Generate all custom query hook files
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
|
|
2
2
|
import { buildCustomQueryString } from './schema-gql-ast';
|
|
3
|
-
import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, getQueryKeyName, } from './type-resolver';
|
|
3
|
+
import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, getQueryKeyName, createTypeTracker, } from './type-resolver';
|
|
4
4
|
import { ucFirst } from './utils';
|
|
5
5
|
/**
|
|
6
6
|
* Generate a custom query hook file
|
|
7
7
|
*/
|
|
8
8
|
export function generateCustomQueryHook(options) {
|
|
9
|
-
const { operation, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true } = options;
|
|
9
|
+
const { operation, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
|
|
10
10
|
const project = createProject();
|
|
11
11
|
const hookName = getOperationHookName(operation.name, 'query');
|
|
12
12
|
const fileName = getOperationFileName(operation.name, 'query');
|
|
@@ -14,6 +14,8 @@ export function generateCustomQueryHook(options) {
|
|
|
14
14
|
const resultTypeName = getOperationResultTypeName(operation.name, 'query');
|
|
15
15
|
const documentConstName = getDocumentConstName(operation.name, 'query');
|
|
16
16
|
const queryKeyName = getQueryKeyName(operation.name);
|
|
17
|
+
// Create type tracker to collect referenced types (with table type awareness)
|
|
18
|
+
const tracker = createTypeTracker({ tableTypeNames });
|
|
17
19
|
// Generate GraphQL document
|
|
18
20
|
const queryDocument = buildCustomQueryString({
|
|
19
21
|
operation,
|
|
@@ -27,6 +29,19 @@ export function generateCustomQueryHook(options) {
|
|
|
27
29
|
? `Custom query hook for ${operation.name}`
|
|
28
30
|
: `Custom query functions for ${operation.name}`;
|
|
29
31
|
sourceFile.insertText(0, createFileHeader(headerText) + '\n\n');
|
|
32
|
+
// Generate variables interface if there are arguments (with tracking)
|
|
33
|
+
let variablesProps = [];
|
|
34
|
+
if (operation.args.length > 0) {
|
|
35
|
+
variablesProps = generateVariablesProperties(operation.args, tracker);
|
|
36
|
+
}
|
|
37
|
+
// Generate result interface (with tracking)
|
|
38
|
+
const resultType = typeRefToTsType(operation.returnType, tracker);
|
|
39
|
+
const resultProps = [
|
|
40
|
+
{ name: operation.name, type: resultType },
|
|
41
|
+
];
|
|
42
|
+
// Get importable types from tracker (separated by source)
|
|
43
|
+
const schemaTypes = tracker.getImportableTypes(); // From schema-types.ts
|
|
44
|
+
const tableTypes = tracker.getTableTypes(); // From types.ts
|
|
30
45
|
// Add imports - conditionally include React Query imports
|
|
31
46
|
const imports = [];
|
|
32
47
|
if (reactQueryEnabled) {
|
|
@@ -41,21 +56,30 @@ export function generateCustomQueryHook(options) {
|
|
|
41
56
|
namedImports: ['execute'],
|
|
42
57
|
typeOnlyNamedImports: ['ExecuteOptions'],
|
|
43
58
|
}));
|
|
59
|
+
// Add types.ts import for table entity types
|
|
60
|
+
if (tableTypes.length > 0) {
|
|
61
|
+
imports.push(createImport({
|
|
62
|
+
moduleSpecifier: '../types',
|
|
63
|
+
typeOnlyNamedImports: tableTypes,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
// Add schema-types import for Input/Payload/Enum types
|
|
67
|
+
if (schemaTypes.length > 0) {
|
|
68
|
+
imports.push(createImport({
|
|
69
|
+
moduleSpecifier: '../schema-types',
|
|
70
|
+
typeOnlyNamedImports: schemaTypes,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
44
73
|
sourceFile.addImportDeclarations(imports);
|
|
45
74
|
// Add query document constant
|
|
46
75
|
sourceFile.addVariableStatement(createConst(documentConstName, '`\n' + queryDocument + '`', {
|
|
47
76
|
docs: ['GraphQL query document'],
|
|
48
77
|
}));
|
|
49
|
-
//
|
|
78
|
+
// Add variables interface
|
|
50
79
|
if (operation.args.length > 0) {
|
|
51
|
-
const variablesProps = generateVariablesProperties(operation.args);
|
|
52
80
|
sourceFile.addInterface(createInterface(variablesTypeName, variablesProps));
|
|
53
81
|
}
|
|
54
|
-
//
|
|
55
|
-
const resultType = typeRefToTsType(operation.returnType);
|
|
56
|
-
const resultProps = [
|
|
57
|
-
{ name: operation.name, type: resultType },
|
|
58
|
-
];
|
|
82
|
+
// Add result interface
|
|
59
83
|
sourceFile.addInterface(createInterface(resultTypeName, resultProps));
|
|
60
84
|
// Query key factory
|
|
61
85
|
if (operation.args.length > 0) {
|
|
@@ -126,10 +150,10 @@ export function generateCustomQueryHook(options) {
|
|
|
126
150
|
/**
|
|
127
151
|
* Generate interface properties from CleanArguments
|
|
128
152
|
*/
|
|
129
|
-
function generateVariablesProperties(args) {
|
|
153
|
+
function generateVariablesProperties(args, tracker) {
|
|
130
154
|
return args.map((arg) => ({
|
|
131
155
|
name: arg.name,
|
|
132
|
-
type: typeRefToTsType(arg.type),
|
|
156
|
+
type: typeRefToTsType(arg.type, tracker),
|
|
133
157
|
optional: !isTypeRequired(arg.type),
|
|
134
158
|
docs: arg.description ? [arg.description] : undefined,
|
|
135
159
|
}));
|
|
@@ -351,7 +375,7 @@ await ${fnName}(queryClient);
|
|
|
351
375
|
* Generate all custom query hook files
|
|
352
376
|
*/
|
|
353
377
|
export function generateAllCustomQueryHooks(options) {
|
|
354
|
-
const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true } = options;
|
|
378
|
+
const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
|
|
355
379
|
return operations
|
|
356
380
|
.filter((op) => op.kind === 'query')
|
|
357
381
|
.map((operation) => generateCustomQueryHook({
|
|
@@ -360,5 +384,6 @@ export function generateAllCustomQueryHooks(options) {
|
|
|
360
384
|
maxDepth,
|
|
361
385
|
skipQueryField,
|
|
362
386
|
reactQueryEnabled,
|
|
387
|
+
tableTypeNames,
|
|
363
388
|
}));
|
|
364
389
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as t from 'gql-ast';
|
|
8
8
|
import { print } from 'graphql';
|
|
9
|
-
import { getTableNames, getAllRowsQueryName, getSingleRowQueryName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getFilterTypeName, getOrderByTypeName, getScalarFields, ucFirst, } from './utils';
|
|
9
|
+
import { getTableNames, getAllRowsQueryName, getSingleRowQueryName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getFilterTypeName, getOrderByTypeName, getScalarFields, getPrimaryKeyInfo, ucFirst, } from './utils';
|
|
10
10
|
// ============================================================================
|
|
11
11
|
// Field selection builders
|
|
12
12
|
// ============================================================================
|
|
@@ -106,16 +106,20 @@ export function buildSingleQueryAST(config) {
|
|
|
106
106
|
const { table } = config;
|
|
107
107
|
const queryName = getSingleRowQueryName(table);
|
|
108
108
|
const scalarFields = getScalarFields(table);
|
|
109
|
-
//
|
|
109
|
+
// Get primary key info dynamically from table constraints
|
|
110
|
+
const pkFields = getPrimaryKeyInfo(table);
|
|
111
|
+
// For simplicity, use first PK field (most common case)
|
|
112
|
+
const pkField = pkFields[0];
|
|
113
|
+
// Variable definitions - use dynamic PK field name and type
|
|
110
114
|
const variableDefinitions = [
|
|
111
115
|
t.variableDefinition({
|
|
112
|
-
variable: t.variable({ name:
|
|
113
|
-
type: t.nonNullType({ type: t.namedType({ type:
|
|
116
|
+
variable: t.variable({ name: pkField.name }),
|
|
117
|
+
type: t.nonNullType({ type: t.namedType({ type: pkField.gqlType }) }),
|
|
114
118
|
}),
|
|
115
119
|
];
|
|
116
|
-
// Query arguments
|
|
120
|
+
// Query arguments - use dynamic PK field name
|
|
117
121
|
const args = [
|
|
118
|
-
t.argument({ name:
|
|
122
|
+
t.argument({ name: pkField.name, value: t.variable({ name: pkField.name }) }),
|
|
119
123
|
];
|
|
120
124
|
// Field selections
|
|
121
125
|
const fieldSelections = createFieldSelections(scalarFields);
|