@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/codegen/queries.js
CHANGED
|
@@ -33,7 +33,7 @@ function generateListQueryHook(table, options = {}) {
|
|
|
33
33
|
// Collect all filter types used by this table's fields
|
|
34
34
|
const filterTypesUsed = new Set();
|
|
35
35
|
for (const field of scalarFields) {
|
|
36
|
-
const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType);
|
|
36
|
+
const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
|
|
37
37
|
if (filterType) {
|
|
38
38
|
filterTypesUsed.add(filterType);
|
|
39
39
|
}
|
|
@@ -71,12 +71,14 @@ function generateListQueryHook(table, options = {}) {
|
|
|
71
71
|
// Generate filter interface
|
|
72
72
|
const fieldFilters = scalarFields
|
|
73
73
|
.map((field) => {
|
|
74
|
-
const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType);
|
|
74
|
+
const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
|
|
75
75
|
return filterType ? { fieldName: field.name, filterType } : null;
|
|
76
76
|
})
|
|
77
77
|
.filter((f) => f !== null);
|
|
78
|
-
|
|
78
|
+
// Note: Not exported to avoid conflicts with schema-types
|
|
79
|
+
sourceFile.addInterface((0, ts_ast_1.createFilterInterface)(filterTypeName, fieldFilters, { isExported: false }));
|
|
79
80
|
// Generate OrderBy type
|
|
81
|
+
// Note: Not exported to avoid conflicts with schema-types
|
|
80
82
|
const orderByValues = [
|
|
81
83
|
...scalarFields.flatMap((f) => [
|
|
82
84
|
`${(0, utils_1.toScreamingSnake)(f.name)}_ASC`,
|
|
@@ -86,7 +88,7 @@ function generateListQueryHook(table, options = {}) {
|
|
|
86
88
|
'PRIMARY_KEY_ASC',
|
|
87
89
|
'PRIMARY_KEY_DESC',
|
|
88
90
|
];
|
|
89
|
-
sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(orderByTypeName, (0, ts_ast_1.createUnionType)(orderByValues)));
|
|
91
|
+
sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(orderByTypeName, (0, ts_ast_1.createUnionType)(orderByValues), { isExported: false }));
|
|
90
92
|
// Variables interface
|
|
91
93
|
const variablesProps = [
|
|
92
94
|
{ name: 'first', type: 'number', optional: true },
|
|
@@ -269,6 +271,13 @@ function generateSingleQueryHook(table, options = {}) {
|
|
|
269
271
|
const { typeName, singularName } = (0, utils_1.getTableNames)(table);
|
|
270
272
|
const hookName = (0, utils_1.getSingleQueryHookName)(table);
|
|
271
273
|
const queryName = (0, utils_1.getSingleRowQueryName)(table);
|
|
274
|
+
// Get primary key info dynamically from table constraints
|
|
275
|
+
const pkFields = (0, utils_1.getPrimaryKeyInfo)(table);
|
|
276
|
+
// For simplicity, use first PK field (most common case)
|
|
277
|
+
// Composite PKs would need more complex handling
|
|
278
|
+
const pkField = pkFields[0];
|
|
279
|
+
const pkName = pkField.name;
|
|
280
|
+
const pkTsType = pkField.tsType;
|
|
272
281
|
// Generate GraphQL document via AST
|
|
273
282
|
const queryAST = (0, gql_ast_1.buildSingleQueryAST)({ table });
|
|
274
283
|
const queryDocument = (0, gql_ast_1.printGraphQL)(queryAST);
|
|
@@ -308,9 +317,9 @@ function generateSingleQueryHook(table, options = {}) {
|
|
|
308
317
|
sourceFile.addStatements('\n// ============================================================================');
|
|
309
318
|
sourceFile.addStatements('// Types');
|
|
310
319
|
sourceFile.addStatements('// ============================================================================\n');
|
|
311
|
-
// Variables interface
|
|
320
|
+
// Variables interface - use dynamic PK field name and type
|
|
312
321
|
sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`, [
|
|
313
|
-
{ name:
|
|
322
|
+
{ name: pkName, type: pkTsType },
|
|
314
323
|
]));
|
|
315
324
|
// Result interface
|
|
316
325
|
sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(singularName)}QueryResult`, [
|
|
@@ -320,20 +329,20 @@ function generateSingleQueryHook(table, options = {}) {
|
|
|
320
329
|
sourceFile.addStatements('\n// ============================================================================');
|
|
321
330
|
sourceFile.addStatements('// Query Key');
|
|
322
331
|
sourceFile.addStatements('// ============================================================================\n');
|
|
323
|
-
// Query key factory
|
|
324
|
-
sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryKey`, `(
|
|
325
|
-
['${typeName.toLowerCase()}', 'detail',
|
|
332
|
+
// Query key factory - use dynamic PK field name and type
|
|
333
|
+
sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryKey`, `(${pkName}: ${pkTsType}) =>
|
|
334
|
+
['${typeName.toLowerCase()}', 'detail', ${pkName}] as const`));
|
|
326
335
|
// Add React Query hook section (only if enabled)
|
|
327
336
|
if (reactQueryEnabled) {
|
|
328
337
|
sourceFile.addStatements('\n// ============================================================================');
|
|
329
338
|
sourceFile.addStatements('// Hook');
|
|
330
339
|
sourceFile.addStatements('// ============================================================================\n');
|
|
331
|
-
// Hook function
|
|
340
|
+
// Hook function - use dynamic PK field name and type
|
|
332
341
|
sourceFile.addFunction({
|
|
333
342
|
name: hookName,
|
|
334
343
|
isExported: true,
|
|
335
344
|
parameters: [
|
|
336
|
-
{ name:
|
|
345
|
+
{ name: pkName, type: pkTsType },
|
|
337
346
|
{
|
|
338
347
|
name: 'options',
|
|
339
348
|
type: `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
|
|
@@ -341,24 +350,24 @@ function generateSingleQueryHook(table, options = {}) {
|
|
|
341
350
|
},
|
|
342
351
|
],
|
|
343
352
|
statements: `return useQuery({
|
|
344
|
-
queryKey: ${queryName}QueryKey(
|
|
353
|
+
queryKey: ${queryName}QueryKey(${pkName}),
|
|
345
354
|
queryFn: () => execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
|
|
346
355
|
${queryName}QueryDocument,
|
|
347
|
-
{
|
|
356
|
+
{ ${pkName} }
|
|
348
357
|
),
|
|
349
|
-
enabled:
|
|
358
|
+
enabled: !!${pkName} && (options?.enabled !== false),
|
|
350
359
|
...options,
|
|
351
360
|
});`,
|
|
352
361
|
docs: [
|
|
353
362
|
{
|
|
354
|
-
description: `Query hook for fetching a single ${typeName} by
|
|
363
|
+
description: `Query hook for fetching a single ${typeName} by primary key
|
|
355
364
|
|
|
356
365
|
@example
|
|
357
366
|
\`\`\`tsx
|
|
358
|
-
const { data, isLoading } = ${hookName}('
|
|
367
|
+
const { data, isLoading } = ${hookName}(${pkTsType === 'string' ? "'value-here'" : '123'});
|
|
359
368
|
|
|
360
369
|
if (data?.${queryName}) {
|
|
361
|
-
console.log(data.${queryName}
|
|
370
|
+
console.log(data.${queryName}.${pkName});
|
|
362
371
|
}
|
|
363
372
|
\`\`\``,
|
|
364
373
|
},
|
|
@@ -369,13 +378,13 @@ if (data?.${queryName}) {
|
|
|
369
378
|
sourceFile.addStatements('\n// ============================================================================');
|
|
370
379
|
sourceFile.addStatements('// Standalone Functions (non-React)');
|
|
371
380
|
sourceFile.addStatements('// ============================================================================\n');
|
|
372
|
-
// Fetch function (standalone, no React)
|
|
381
|
+
// Fetch function (standalone, no React) - use dynamic PK
|
|
373
382
|
sourceFile.addFunction({
|
|
374
383
|
name: `fetch${(0, utils_1.ucFirst)(singularName)}Query`,
|
|
375
384
|
isExported: true,
|
|
376
385
|
isAsync: true,
|
|
377
386
|
parameters: [
|
|
378
|
-
{ name:
|
|
387
|
+
{ name: pkName, type: pkTsType },
|
|
379
388
|
{
|
|
380
389
|
name: 'options',
|
|
381
390
|
type: 'ExecuteOptions',
|
|
@@ -385,21 +394,21 @@ if (data?.${queryName}) {
|
|
|
385
394
|
returnType: `Promise<${(0, utils_1.ucFirst)(singularName)}QueryResult>`,
|
|
386
395
|
statements: `return execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
|
|
387
396
|
${queryName}QueryDocument,
|
|
388
|
-
{
|
|
397
|
+
{ ${pkName} },
|
|
389
398
|
options
|
|
390
399
|
);`,
|
|
391
400
|
docs: [
|
|
392
401
|
{
|
|
393
|
-
description: `Fetch a single ${typeName} by
|
|
402
|
+
description: `Fetch a single ${typeName} by primary key without React hooks
|
|
394
403
|
|
|
395
404
|
@example
|
|
396
405
|
\`\`\`ts
|
|
397
|
-
const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('
|
|
406
|
+
const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query(${pkTsType === 'string' ? "'value-here'" : '123'});
|
|
398
407
|
\`\`\``,
|
|
399
408
|
},
|
|
400
409
|
],
|
|
401
410
|
});
|
|
402
|
-
// Prefetch function (for SSR/QueryClient) - only if React Query is enabled
|
|
411
|
+
// Prefetch function (for SSR/QueryClient) - only if React Query is enabled, use dynamic PK
|
|
403
412
|
if (reactQueryEnabled) {
|
|
404
413
|
sourceFile.addFunction({
|
|
405
414
|
name: `prefetch${(0, utils_1.ucFirst)(singularName)}Query`,
|
|
@@ -407,7 +416,7 @@ const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('uuid-here');
|
|
|
407
416
|
isAsync: true,
|
|
408
417
|
parameters: [
|
|
409
418
|
{ name: 'queryClient', type: 'QueryClient' },
|
|
410
|
-
{ name:
|
|
419
|
+
{ name: pkName, type: pkTsType },
|
|
411
420
|
{
|
|
412
421
|
name: 'options',
|
|
413
422
|
type: 'ExecuteOptions',
|
|
@@ -416,10 +425,10 @@ const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('uuid-here');
|
|
|
416
425
|
],
|
|
417
426
|
returnType: 'Promise<void>',
|
|
418
427
|
statements: `await queryClient.prefetchQuery({
|
|
419
|
-
queryKey: ${queryName}QueryKey(
|
|
428
|
+
queryKey: ${queryName}QueryKey(${pkName}),
|
|
420
429
|
queryFn: () => execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
|
|
421
430
|
${queryName}QueryDocument,
|
|
422
|
-
{
|
|
431
|
+
{ ${pkName} },
|
|
423
432
|
options
|
|
424
433
|
),
|
|
425
434
|
});`,
|
|
@@ -429,7 +438,7 @@ const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('uuid-here');
|
|
|
429
438
|
|
|
430
439
|
@example
|
|
431
440
|
\`\`\`ts
|
|
432
|
-
await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, '
|
|
441
|
+
await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, ${pkTsType === 'string' ? "'value-here'" : '123'});
|
|
433
442
|
\`\`\``,
|
|
434
443
|
},
|
|
435
444
|
],
|
package/cli/codegen/scalars.d.ts
CHANGED
|
@@ -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;
|
package/cli/codegen/scalars.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Shared scalar mappings for code generation
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SCALAR_NAMES = exports.SCALAR_FILTER_MAP = exports.SCALAR_TS_MAP = void 0;
|
|
6
|
+
exports.BASE_FILTER_TYPE_NAMES = exports.SCALAR_NAMES = exports.SCALAR_FILTER_MAP = exports.SCALAR_TS_MAP = void 0;
|
|
7
7
|
exports.scalarToTsType = scalarToTsType;
|
|
8
8
|
exports.scalarToFilterType = scalarToFilterType;
|
|
9
9
|
exports.SCALAR_TS_MAP = {
|
|
@@ -37,6 +37,8 @@ exports.SCALAR_TS_MAP = {
|
|
|
37
37
|
MacAddr: 'string',
|
|
38
38
|
TsVector: 'string',
|
|
39
39
|
TsQuery: 'string',
|
|
40
|
+
// File upload
|
|
41
|
+
Upload: 'File',
|
|
40
42
|
};
|
|
41
43
|
exports.SCALAR_FILTER_MAP = {
|
|
42
44
|
String: 'StringFilter',
|
|
@@ -57,15 +59,21 @@ exports.SCALAR_FILTER_MAP = {
|
|
|
57
59
|
Interval: 'StringFilter',
|
|
58
60
|
};
|
|
59
61
|
exports.SCALAR_NAMES = new Set(Object.keys(exports.SCALAR_TS_MAP));
|
|
62
|
+
/** Scalars that have list filter variants (e.g., StringListFilter) */
|
|
63
|
+
const LIST_FILTER_SCALARS = new Set(['String', 'Int', 'UUID']);
|
|
64
|
+
/** All base filter type names - skip these in schema-types.ts to avoid duplicates */
|
|
65
|
+
exports.BASE_FILTER_TYPE_NAMES = new Set([
|
|
66
|
+
...new Set(Object.values(exports.SCALAR_FILTER_MAP)),
|
|
67
|
+
...Array.from(LIST_FILTER_SCALARS).map((s) => `${s}ListFilter`),
|
|
68
|
+
]);
|
|
60
69
|
function scalarToTsType(scalarName, options = {}) {
|
|
61
|
-
|
|
62
|
-
if (override)
|
|
63
|
-
return override;
|
|
64
|
-
const mapped = exports.SCALAR_TS_MAP[scalarName];
|
|
65
|
-
if (mapped)
|
|
66
|
-
return mapped;
|
|
67
|
-
return options.unknownScalar === 'unknown' ? 'unknown' : scalarName;
|
|
70
|
+
return options.overrides?.[scalarName] ?? exports.SCALAR_TS_MAP[scalarName] ?? (options.unknownScalar === 'unknown' ? 'unknown' : scalarName);
|
|
68
71
|
}
|
|
69
|
-
|
|
72
|
+
/** Get the filter type for a scalar (handles both scalar and array types) */
|
|
73
|
+
function scalarToFilterType(scalarName, isArray = false) {
|
|
74
|
+
const baseName = scalarName === 'ID' ? 'UUID' : scalarName;
|
|
75
|
+
if (isArray) {
|
|
76
|
+
return LIST_FILTER_SCALARS.has(baseName) ? `${baseName}ListFilter` : null;
|
|
77
|
+
}
|
|
70
78
|
return exports.SCALAR_FILTER_MAP[scalarName] ?? null;
|
|
71
79
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateSchemaTypesFile = generateSchemaTypesFile;
|
|
4
|
+
const ts_ast_1 = require("./ts-ast");
|
|
5
|
+
const type_resolver_1 = require("./type-resolver");
|
|
6
|
+
const scalars_1 = require("./scalars");
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Constants
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Types that should not be generated (scalars, built-ins, types generated elsewhere)
|
|
12
|
+
*/
|
|
13
|
+
const SKIP_TYPES = new Set([
|
|
14
|
+
...scalars_1.SCALAR_NAMES,
|
|
15
|
+
// GraphQL built-ins
|
|
16
|
+
'Query',
|
|
17
|
+
'Mutation',
|
|
18
|
+
'Subscription',
|
|
19
|
+
'__Schema',
|
|
20
|
+
'__Type',
|
|
21
|
+
'__Field',
|
|
22
|
+
'__InputValue',
|
|
23
|
+
'__EnumValue',
|
|
24
|
+
'__Directive',
|
|
25
|
+
// Note: PageInfo and Cursor are NOT skipped - they're needed by Connection types
|
|
26
|
+
// Base filter types (generated in types.ts via filters.ts)
|
|
27
|
+
...scalars_1.BASE_FILTER_TYPE_NAMES,
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* Type name patterns to skip (regex patterns)
|
|
31
|
+
*
|
|
32
|
+
* Note: We intentionally DO NOT skip Connection, Edge, Filter, or Patch types
|
|
33
|
+
* because they may be referenced by custom operations or payload types.
|
|
34
|
+
* Only skip Condition and OrderBy which are typically not needed.
|
|
35
|
+
*/
|
|
36
|
+
const SKIP_TYPE_PATTERNS = [
|
|
37
|
+
/Condition$/, // e.g., UserCondition (filter conditions are separate)
|
|
38
|
+
/OrderBy$/, // e.g., UsersOrderBy (ordering is handled separately)
|
|
39
|
+
];
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Type Conversion Utilities
|
|
42
|
+
// ============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Convert a CleanTypeRef to TypeScript type string
|
|
45
|
+
*/
|
|
46
|
+
function typeRefToTs(typeRef) {
|
|
47
|
+
if (typeRef.kind === 'NON_NULL') {
|
|
48
|
+
if (typeRef.ofType) {
|
|
49
|
+
return typeRefToTs(typeRef.ofType);
|
|
50
|
+
}
|
|
51
|
+
return typeRef.name ?? 'unknown';
|
|
52
|
+
}
|
|
53
|
+
if (typeRef.kind === 'LIST') {
|
|
54
|
+
if (typeRef.ofType) {
|
|
55
|
+
return `${typeRefToTs(typeRef.ofType)}[]`;
|
|
56
|
+
}
|
|
57
|
+
return 'unknown[]';
|
|
58
|
+
}
|
|
59
|
+
// Scalar or named type
|
|
60
|
+
const name = typeRef.name ?? 'unknown';
|
|
61
|
+
return (0, scalars_1.scalarToTsType)(name, { unknownScalar: 'name' });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check if a type is required (NON_NULL)
|
|
65
|
+
*/
|
|
66
|
+
function isRequired(typeRef) {
|
|
67
|
+
return typeRef.kind === 'NON_NULL';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if a type should be skipped
|
|
71
|
+
*/
|
|
72
|
+
function shouldSkipType(typeName, tableTypeNames) {
|
|
73
|
+
// Skip scalars and built-ins
|
|
74
|
+
if (SKIP_TYPES.has(typeName))
|
|
75
|
+
return true;
|
|
76
|
+
// Skip table entity types (already in types.ts)
|
|
77
|
+
if (tableTypeNames.has(typeName))
|
|
78
|
+
return true;
|
|
79
|
+
// Skip types matching patterns
|
|
80
|
+
for (const pattern of SKIP_TYPE_PATTERNS) {
|
|
81
|
+
if (pattern.test(typeName))
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
// Skip table-specific types that would conflict with inline types in table-based hooks.
|
|
85
|
+
// Note: Patch and CreateInput are now NOT exported from hooks (isExported: false),
|
|
86
|
+
// so we only skip Filter types here.
|
|
87
|
+
// The Filter and OrderBy types are generated inline (non-exported) by table query hooks,
|
|
88
|
+
// but schema-types should still generate them for custom operations that need them.
|
|
89
|
+
// Actually, we don't skip any table-based types now since hooks don't export them.
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// ENUM Types Generator
|
|
94
|
+
// ============================================================================
|
|
95
|
+
/**
|
|
96
|
+
* Add ENUM types to source file
|
|
97
|
+
*/
|
|
98
|
+
function addEnumTypes(sourceFile, typeRegistry, tableTypeNames) {
|
|
99
|
+
const generatedTypes = new Set();
|
|
100
|
+
(0, ts_ast_1.addSectionComment)(sourceFile, 'Enum Types');
|
|
101
|
+
for (const [typeName, typeInfo] of typeRegistry) {
|
|
102
|
+
if (typeInfo.kind !== 'ENUM')
|
|
103
|
+
continue;
|
|
104
|
+
if (shouldSkipType(typeName, tableTypeNames))
|
|
105
|
+
continue;
|
|
106
|
+
if (!typeInfo.enumValues || typeInfo.enumValues.length === 0)
|
|
107
|
+
continue;
|
|
108
|
+
const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
|
|
109
|
+
sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(typeName, values));
|
|
110
|
+
generatedTypes.add(typeName);
|
|
111
|
+
}
|
|
112
|
+
return generatedTypes;
|
|
113
|
+
}
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// INPUT_OBJECT Types Generator
|
|
116
|
+
// ============================================================================
|
|
117
|
+
/**
|
|
118
|
+
* Add INPUT_OBJECT types to source file
|
|
119
|
+
* Uses iteration to handle nested input types
|
|
120
|
+
*/
|
|
121
|
+
function addInputObjectTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
|
|
122
|
+
const generatedTypes = new Set(alreadyGenerated);
|
|
123
|
+
const typesToGenerate = new Set();
|
|
124
|
+
// Collect all INPUT_OBJECT types
|
|
125
|
+
for (const [typeName, typeInfo] of typeRegistry) {
|
|
126
|
+
if (typeInfo.kind !== 'INPUT_OBJECT')
|
|
127
|
+
continue;
|
|
128
|
+
if (shouldSkipType(typeName, tableTypeNames))
|
|
129
|
+
continue;
|
|
130
|
+
if (generatedTypes.has(typeName))
|
|
131
|
+
continue;
|
|
132
|
+
typesToGenerate.add(typeName);
|
|
133
|
+
}
|
|
134
|
+
if (typesToGenerate.size === 0)
|
|
135
|
+
return generatedTypes;
|
|
136
|
+
(0, ts_ast_1.addSectionComment)(sourceFile, 'Input Object Types');
|
|
137
|
+
// Process all types - no artificial limit
|
|
138
|
+
while (typesToGenerate.size > 0) {
|
|
139
|
+
const typeNameResult = typesToGenerate.values().next();
|
|
140
|
+
if (typeNameResult.done)
|
|
141
|
+
break;
|
|
142
|
+
const typeName = typeNameResult.value;
|
|
143
|
+
typesToGenerate.delete(typeName);
|
|
144
|
+
if (generatedTypes.has(typeName))
|
|
145
|
+
continue;
|
|
146
|
+
const typeInfo = typeRegistry.get(typeName);
|
|
147
|
+
if (!typeInfo || typeInfo.kind !== 'INPUT_OBJECT')
|
|
148
|
+
continue;
|
|
149
|
+
generatedTypes.add(typeName);
|
|
150
|
+
if (typeInfo.inputFields && typeInfo.inputFields.length > 0) {
|
|
151
|
+
const properties = [];
|
|
152
|
+
for (const field of typeInfo.inputFields) {
|
|
153
|
+
const optional = !isRequired(field.type);
|
|
154
|
+
const tsType = typeRefToTs(field.type);
|
|
155
|
+
properties.push({
|
|
156
|
+
name: field.name,
|
|
157
|
+
type: tsType,
|
|
158
|
+
optional,
|
|
159
|
+
docs: field.description ? [field.description] : undefined,
|
|
160
|
+
});
|
|
161
|
+
// Follow nested Input types
|
|
162
|
+
const baseType = (0, type_resolver_1.getTypeBaseName)(field.type);
|
|
163
|
+
if (baseType &&
|
|
164
|
+
!generatedTypes.has(baseType) &&
|
|
165
|
+
!shouldSkipType(baseType, tableTypeNames)) {
|
|
166
|
+
const nestedType = typeRegistry.get(baseType);
|
|
167
|
+
if (nestedType?.kind === 'INPUT_OBJECT') {
|
|
168
|
+
typesToGenerate.add(baseType);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
sourceFile.addInterface((0, ts_ast_1.createInterface)(typeName, properties));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Empty input object
|
|
176
|
+
sourceFile.addInterface((0, ts_ast_1.createInterface)(typeName, []));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return generatedTypes;
|
|
180
|
+
}
|
|
181
|
+
// ============================================================================
|
|
182
|
+
// UNION Types Generator
|
|
183
|
+
// ============================================================================
|
|
184
|
+
/**
|
|
185
|
+
* Add UNION types to source file
|
|
186
|
+
*/
|
|
187
|
+
function addUnionTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
|
|
188
|
+
const generatedTypes = new Set(alreadyGenerated);
|
|
189
|
+
const unionTypesToGenerate = new Set();
|
|
190
|
+
// Collect all UNION types
|
|
191
|
+
for (const [typeName, typeInfo] of typeRegistry) {
|
|
192
|
+
if (typeInfo.kind !== 'UNION')
|
|
193
|
+
continue;
|
|
194
|
+
if (shouldSkipType(typeName, tableTypeNames))
|
|
195
|
+
continue;
|
|
196
|
+
if (generatedTypes.has(typeName))
|
|
197
|
+
continue;
|
|
198
|
+
unionTypesToGenerate.add(typeName);
|
|
199
|
+
}
|
|
200
|
+
if (unionTypesToGenerate.size === 0)
|
|
201
|
+
return generatedTypes;
|
|
202
|
+
(0, ts_ast_1.addSectionComment)(sourceFile, 'Union Types');
|
|
203
|
+
for (const typeName of unionTypesToGenerate) {
|
|
204
|
+
const typeInfo = typeRegistry.get(typeName);
|
|
205
|
+
if (!typeInfo || typeInfo.kind !== 'UNION')
|
|
206
|
+
continue;
|
|
207
|
+
if (!typeInfo.possibleTypes || typeInfo.possibleTypes.length === 0)
|
|
208
|
+
continue;
|
|
209
|
+
// Generate union type as TypeScript union
|
|
210
|
+
const unionMembers = typeInfo.possibleTypes.join(' | ');
|
|
211
|
+
sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(typeName, unionMembers));
|
|
212
|
+
generatedTypes.add(typeName);
|
|
213
|
+
}
|
|
214
|
+
return generatedTypes;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Collect return types from Query and Mutation root types
|
|
218
|
+
* This dynamically discovers what OBJECT types need to be generated
|
|
219
|
+
* based on actual schema structure, not pattern matching
|
|
220
|
+
*/
|
|
221
|
+
function collectReturnTypesFromRootTypes(typeRegistry, tableTypeNames) {
|
|
222
|
+
const returnTypes = new Set();
|
|
223
|
+
// Get Query and Mutation root types
|
|
224
|
+
const queryType = typeRegistry.get('Query');
|
|
225
|
+
const mutationType = typeRegistry.get('Mutation');
|
|
226
|
+
const processFields = (fields) => {
|
|
227
|
+
if (!fields)
|
|
228
|
+
return;
|
|
229
|
+
for (const field of fields) {
|
|
230
|
+
const baseType = (0, type_resolver_1.getTypeBaseName)(field.type);
|
|
231
|
+
if (baseType && !shouldSkipType(baseType, tableTypeNames)) {
|
|
232
|
+
const typeInfo = typeRegistry.get(baseType);
|
|
233
|
+
if (typeInfo?.kind === 'OBJECT') {
|
|
234
|
+
returnTypes.add(baseType);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
if (queryType?.fields)
|
|
240
|
+
processFields(queryType.fields);
|
|
241
|
+
if (mutationType?.fields)
|
|
242
|
+
processFields(mutationType.fields);
|
|
243
|
+
return returnTypes;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Add Payload OBJECT types to source file
|
|
247
|
+
* These are return types from mutations (e.g., LoginPayload, BootstrapUserPayload)
|
|
248
|
+
*
|
|
249
|
+
* Also tracks which table entity types are referenced so they can be imported.
|
|
250
|
+
*
|
|
251
|
+
* Uses dynamic type discovery from Query/Mutation return types instead of pattern matching.
|
|
252
|
+
*/
|
|
253
|
+
function addPayloadObjectTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
|
|
254
|
+
const generatedTypes = new Set(alreadyGenerated);
|
|
255
|
+
const referencedTableTypes = new Set();
|
|
256
|
+
// Dynamically collect return types from Query and Mutation
|
|
257
|
+
const typesToGenerate = collectReturnTypesFromRootTypes(typeRegistry, tableTypeNames);
|
|
258
|
+
// Filter out already generated types
|
|
259
|
+
for (const typeName of Array.from(typesToGenerate)) {
|
|
260
|
+
if (generatedTypes.has(typeName)) {
|
|
261
|
+
typesToGenerate.delete(typeName);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (typesToGenerate.size === 0) {
|
|
265
|
+
return { generatedTypes, referencedTableTypes };
|
|
266
|
+
}
|
|
267
|
+
(0, ts_ast_1.addSectionComment)(sourceFile, 'Payload/Return Object Types');
|
|
268
|
+
// Process all types - no artificial limit
|
|
269
|
+
while (typesToGenerate.size > 0) {
|
|
270
|
+
const typeNameResult = typesToGenerate.values().next();
|
|
271
|
+
if (typeNameResult.done)
|
|
272
|
+
break;
|
|
273
|
+
const typeName = typeNameResult.value;
|
|
274
|
+
typesToGenerate.delete(typeName);
|
|
275
|
+
if (generatedTypes.has(typeName))
|
|
276
|
+
continue;
|
|
277
|
+
const typeInfo = typeRegistry.get(typeName);
|
|
278
|
+
if (!typeInfo || typeInfo.kind !== 'OBJECT')
|
|
279
|
+
continue;
|
|
280
|
+
generatedTypes.add(typeName);
|
|
281
|
+
if (typeInfo.fields && typeInfo.fields.length > 0) {
|
|
282
|
+
const properties = [];
|
|
283
|
+
for (const field of typeInfo.fields) {
|
|
284
|
+
const baseType = (0, type_resolver_1.getTypeBaseName)(field.type);
|
|
285
|
+
// Skip Query and Mutation fields
|
|
286
|
+
if (baseType === 'Query' || baseType === 'Mutation')
|
|
287
|
+
continue;
|
|
288
|
+
const tsType = typeRefToTs(field.type);
|
|
289
|
+
const isNullable = !isRequired(field.type);
|
|
290
|
+
properties.push({
|
|
291
|
+
name: field.name,
|
|
292
|
+
type: isNullable ? `${tsType} | null` : tsType,
|
|
293
|
+
optional: isNullable,
|
|
294
|
+
docs: field.description ? [field.description] : undefined,
|
|
295
|
+
});
|
|
296
|
+
// Track table entity types that are referenced
|
|
297
|
+
if (baseType && tableTypeNames.has(baseType)) {
|
|
298
|
+
referencedTableTypes.add(baseType);
|
|
299
|
+
}
|
|
300
|
+
// Follow nested OBJECT types that aren't table types
|
|
301
|
+
if (baseType &&
|
|
302
|
+
!generatedTypes.has(baseType) &&
|
|
303
|
+
!shouldSkipType(baseType, tableTypeNames)) {
|
|
304
|
+
const nestedType = typeRegistry.get(baseType);
|
|
305
|
+
if (nestedType?.kind === 'OBJECT') {
|
|
306
|
+
typesToGenerate.add(baseType);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
sourceFile.addInterface((0, ts_ast_1.createInterface)(typeName, properties));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// Empty payload object
|
|
314
|
+
sourceFile.addInterface((0, ts_ast_1.createInterface)(typeName, []));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return { generatedTypes, referencedTableTypes };
|
|
318
|
+
}
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Main Generator
|
|
321
|
+
// ============================================================================
|
|
322
|
+
/**
|
|
323
|
+
* Generate comprehensive schema-types.ts file using ts-morph AST
|
|
324
|
+
*
|
|
325
|
+
* This generates all Input/Payload/Enum types from the TypeRegistry
|
|
326
|
+
* that are needed by custom mutation/query hooks.
|
|
327
|
+
*/
|
|
328
|
+
function generateSchemaTypesFile(options) {
|
|
329
|
+
const { typeRegistry, tableTypeNames } = options;
|
|
330
|
+
const project = (0, ts_ast_1.createProject)();
|
|
331
|
+
const sourceFile = (0, ts_ast_1.createSourceFile)(project, 'schema-types.ts');
|
|
332
|
+
// Add file header
|
|
333
|
+
sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)('GraphQL schema types for custom operations') + '\n');
|
|
334
|
+
// Track all generated types
|
|
335
|
+
let generatedTypes = new Set();
|
|
336
|
+
// 1. Generate ENUM types
|
|
337
|
+
const enumTypes = addEnumTypes(sourceFile, typeRegistry, tableTypeNames);
|
|
338
|
+
generatedTypes = new Set([...generatedTypes, ...enumTypes]);
|
|
339
|
+
// 2. Generate UNION types
|
|
340
|
+
const unionTypes = addUnionTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
|
|
341
|
+
generatedTypes = new Set([...generatedTypes, ...unionTypes]);
|
|
342
|
+
// 3. Generate INPUT_OBJECT types
|
|
343
|
+
const inputTypes = addInputObjectTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
|
|
344
|
+
generatedTypes = new Set([...generatedTypes, ...inputTypes]);
|
|
345
|
+
// 4. Generate Payload OBJECT types
|
|
346
|
+
const payloadResult = addPayloadObjectTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
|
|
347
|
+
// 5. Add imports from types.ts (table entity types + base filter types)
|
|
348
|
+
const referencedTableTypes = Array.from(payloadResult.referencedTableTypes).sort();
|
|
349
|
+
// Always import base filter types since generated Filter interfaces reference them
|
|
350
|
+
const baseFilterImports = Array.from(scalars_1.BASE_FILTER_TYPE_NAMES).sort();
|
|
351
|
+
const allTypesImports = [...referencedTableTypes, ...baseFilterImports];
|
|
352
|
+
if (allTypesImports.length > 0) {
|
|
353
|
+
// Insert import after the file header comment
|
|
354
|
+
const importStatement = `import type { ${allTypesImports.join(', ')} } from './types';\n\n`;
|
|
355
|
+
// Find position after header (after first */ + newlines)
|
|
356
|
+
const headerEndIndex = sourceFile.getFullText().indexOf('*/') + 3;
|
|
357
|
+
sourceFile.insertText(headerEndIndex, '\n' + importStatement);
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
fileName: 'schema-types.ts',
|
|
361
|
+
content: (0, ts_ast_1.getMinimalFormattedOutput)(sourceFile),
|
|
362
|
+
generatedEnums: Array.from(enumTypes).sort(),
|
|
363
|
+
referencedTableTypes,
|
|
364
|
+
};
|
|
365
|
+
}
|
package/cli/codegen/ts-ast.d.ts
CHANGED
|
@@ -66,7 +66,9 @@ export declare function createInterface(name: string, properties: InterfacePrope
|
|
|
66
66
|
export declare function createFilterInterface(name: string, fieldFilters: Array<{
|
|
67
67
|
fieldName: string;
|
|
68
68
|
filterType: string;
|
|
69
|
-
}
|
|
69
|
+
}>, options?: {
|
|
70
|
+
isExported?: boolean;
|
|
71
|
+
}): InterfaceDeclarationStructure;
|
|
70
72
|
/**
|
|
71
73
|
* Create type alias declaration structure
|
|
72
74
|
*/
|
package/cli/codegen/ts-ast.js
CHANGED
|
@@ -163,7 +163,7 @@ function createInterface(name, properties, options) {
|
|
|
163
163
|
/**
|
|
164
164
|
* Create filter interface with standard PostGraphile operators
|
|
165
165
|
*/
|
|
166
|
-
function createFilterInterface(name, fieldFilters) {
|
|
166
|
+
function createFilterInterface(name, fieldFilters, options) {
|
|
167
167
|
const properties = [
|
|
168
168
|
...fieldFilters.map((f) => ({
|
|
169
169
|
name: f.fieldName,
|
|
@@ -174,7 +174,7 @@ function createFilterInterface(name, fieldFilters) {
|
|
|
174
174
|
{ name: 'or', type: `${name}[]`, optional: true, docs: ['Logical OR'] },
|
|
175
175
|
{ name: 'not', type: name, optional: true, docs: ['Logical NOT'] },
|
|
176
176
|
];
|
|
177
|
-
return createInterface(name, properties);
|
|
177
|
+
return createInterface(name, properties, { isExported: options?.isExported ?? true });
|
|
178
178
|
}
|
|
179
179
|
// ============================================================================
|
|
180
180
|
// Type alias builders
|