@constructive-io/graphql-codegen 2.22.1 → 2.23.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 +5 -1
- package/cli/codegen/barrel.js +13 -11
- package/cli/codegen/index.d.ts +3 -3
- package/cli/codegen/index.js +15 -9
- package/cli/codegen/orm/client-generator.js +3 -2
- package/cli/codegen/orm/custom-ops-generator.js +17 -4
- package/cli/codegen/orm/input-types-generator.js +129 -18
- package/cli/codegen/orm/model-generator.js +2 -1
- package/cli/codegen/orm/query-builder.d.ts +1 -1
- package/cli/codegen/orm/query-builder.js +2 -2
- package/cli/codegen/schema-types-generator.js +5 -5
- package/cli/codegen/utils.d.ts +6 -1
- package/cli/codegen/utils.js +23 -8
- package/cli/commands/generate-orm.d.ts +5 -3
- package/cli/commands/generate-orm.js +65 -84
- package/cli/commands/generate.d.ts +2 -0
- package/cli/commands/generate.js +66 -87
- package/cli/commands/shared.d.ts +74 -0
- package/cli/commands/shared.js +88 -0
- package/cli/index.js +75 -45
- package/cli/introspect/index.d.ts +8 -5
- package/cli/introspect/index.js +19 -7
- package/cli/introspect/infer-tables.d.ts +51 -0
- package/cli/introspect/infer-tables.js +550 -0
- package/cli/introspect/pluralize.d.ts +30 -0
- package/cli/introspect/pluralize.js +124 -0
- package/cli/introspect/source/endpoint.d.ts +34 -0
- package/cli/introspect/source/endpoint.js +35 -0
- package/cli/introspect/source/file.d.ts +20 -0
- package/cli/introspect/source/file.js +103 -0
- package/cli/introspect/source/index.d.ts +48 -0
- package/cli/introspect/source/index.js +72 -0
- package/cli/introspect/source/types.d.ts +58 -0
- package/cli/introspect/source/types.js +27 -0
- package/cli/introspect/transform.d.ts +5 -6
- package/cli/introspect/transform.js +0 -173
- package/cli/watch/cache.d.ts +3 -4
- package/cli/watch/cache.js +6 -10
- package/cli/watch/poller.d.ts +1 -2
- package/cli/watch/poller.js +27 -45
- package/cli/watch/types.d.ts +0 -3
- package/esm/cli/codegen/barrel.d.ts +5 -1
- package/esm/cli/codegen/barrel.js +13 -11
- package/esm/cli/codegen/index.d.ts +3 -3
- package/esm/cli/codegen/index.js +18 -12
- package/esm/cli/codegen/orm/client-generator.js +3 -2
- package/esm/cli/codegen/orm/custom-ops-generator.js +18 -5
- package/esm/cli/codegen/orm/input-types-generator.js +130 -19
- package/esm/cli/codegen/orm/model-generator.js +3 -2
- package/esm/cli/codegen/orm/query-builder.d.ts +1 -1
- package/esm/cli/codegen/orm/query-builder.js +2 -2
- package/esm/cli/codegen/schema-types-generator.js +6 -6
- package/esm/cli/codegen/utils.d.ts +6 -1
- package/esm/cli/codegen/utils.js +22 -8
- package/esm/cli/commands/generate-orm.d.ts +5 -3
- package/esm/cli/commands/generate-orm.js +65 -84
- package/esm/cli/commands/generate.d.ts +2 -0
- package/esm/cli/commands/generate.js +66 -87
- package/esm/cli/commands/shared.d.ts +74 -0
- package/esm/cli/commands/shared.js +84 -0
- package/esm/cli/index.js +76 -46
- package/esm/cli/introspect/index.d.ts +8 -5
- package/esm/cli/introspect/index.js +10 -3
- package/esm/cli/introspect/infer-tables.d.ts +51 -0
- package/esm/cli/introspect/infer-tables.js +547 -0
- package/esm/cli/introspect/pluralize.d.ts +30 -0
- package/esm/cli/introspect/pluralize.js +83 -0
- package/esm/cli/introspect/source/endpoint.d.ts +34 -0
- package/esm/cli/introspect/source/endpoint.js +31 -0
- package/esm/cli/introspect/source/file.d.ts +20 -0
- package/esm/cli/introspect/source/file.js +66 -0
- package/esm/cli/introspect/source/index.d.ts +48 -0
- package/esm/cli/introspect/source/index.js +54 -0
- package/esm/cli/introspect/source/types.d.ts +58 -0
- package/esm/cli/introspect/source/types.js +23 -0
- package/esm/cli/introspect/transform.d.ts +5 -6
- package/esm/cli/introspect/transform.js +0 -172
- package/esm/cli/watch/cache.d.ts +3 -4
- package/esm/cli/watch/cache.js +7 -11
- package/esm/cli/watch/poller.d.ts +1 -2
- package/esm/cli/watch/poller.js +28 -46
- package/esm/cli/watch/types.d.ts +0 -3
- package/esm/types/config.d.ts +21 -5
- package/esm/types/config.js +2 -1
- package/package.json +4 -2
- package/types/config.d.ts +21 -5
- package/types/config.js +2 -1
- package/cli/introspect/fetch-meta.d.ts +0 -31
- package/cli/introspect/fetch-meta.js +0 -108
- package/cli/introspect/meta-query.d.ts +0 -111
- package/cli/introspect/meta-query.js +0 -191
- package/esm/cli/introspect/fetch-meta.d.ts +0 -31
- package/esm/cli/introspect/fetch-meta.js +0 -104
- package/esm/cli/introspect/meta-query.d.ts +0 -111
- package/esm/cli/introspect/meta-query.js +0 -188
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { createProject, createSourceFile, getMinimalFormattedOutput, createFileHeader, createInterface, createTypeAlias, addSectionComment, } from '../ts-ast';
|
|
2
|
-
import { getTableNames, getFilterTypeName, getOrderByTypeName, isRelationField, } from '../utils';
|
|
2
|
+
import { getTableNames, getFilterTypeName, getConditionTypeName, getOrderByTypeName, isRelationField, } from '../utils';
|
|
3
|
+
import { pluralize } from '../../introspect/pluralize';
|
|
3
4
|
import { getTypeBaseName } from '../type-resolver';
|
|
4
5
|
import { scalarToTsType, scalarToFilterType } from '../scalars';
|
|
5
6
|
// ============================================================================
|
|
6
7
|
// Constants
|
|
7
8
|
// ============================================================================
|
|
8
9
|
/** Fields excluded from Create/Update inputs (auto-generated or system fields) */
|
|
9
|
-
const EXCLUDED_MUTATION_FIELDS = [
|
|
10
|
+
const EXCLUDED_MUTATION_FIELDS = [
|
|
11
|
+
'id',
|
|
12
|
+
'createdAt',
|
|
13
|
+
'updatedAt',
|
|
14
|
+
'nodeId',
|
|
15
|
+
];
|
|
10
16
|
// ============================================================================
|
|
11
17
|
// Type Conversion Utilities
|
|
12
18
|
// ============================================================================
|
|
@@ -53,18 +59,58 @@ function isRequired(typeRef) {
|
|
|
53
59
|
}
|
|
54
60
|
/** Configuration for all scalar filter types - matches PostGraphile's generated filters */
|
|
55
61
|
const SCALAR_FILTER_CONFIGS = [
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
{
|
|
63
|
+
name: 'StringFilter',
|
|
64
|
+
tsType: 'string',
|
|
65
|
+
operators: ['equality', 'distinct', 'inArray', 'comparison', 'string'],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'IntFilter',
|
|
69
|
+
tsType: 'number',
|
|
70
|
+
operators: ['equality', 'distinct', 'inArray', 'comparison'],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'FloatFilter',
|
|
74
|
+
tsType: 'number',
|
|
75
|
+
operators: ['equality', 'distinct', 'inArray', 'comparison'],
|
|
76
|
+
},
|
|
59
77
|
{ name: 'BooleanFilter', tsType: 'boolean', operators: ['equality'] },
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
78
|
+
{
|
|
79
|
+
name: 'UUIDFilter',
|
|
80
|
+
tsType: 'string',
|
|
81
|
+
operators: ['equality', 'distinct', 'inArray'],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'DatetimeFilter',
|
|
85
|
+
tsType: 'string',
|
|
86
|
+
operators: ['equality', 'distinct', 'inArray', 'comparison'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'DateFilter',
|
|
90
|
+
tsType: 'string',
|
|
91
|
+
operators: ['equality', 'distinct', 'inArray', 'comparison'],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'JSONFilter',
|
|
95
|
+
tsType: 'Record<string, unknown>',
|
|
96
|
+
operators: ['equality', 'distinct', 'json'],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'BigIntFilter',
|
|
100
|
+
tsType: 'string',
|
|
101
|
+
operators: ['equality', 'distinct', 'inArray', 'comparison'],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'BigFloatFilter',
|
|
105
|
+
tsType: 'string',
|
|
106
|
+
operators: ['equality', 'distinct', 'inArray', 'comparison'],
|
|
107
|
+
},
|
|
66
108
|
{ name: 'BitStringFilter', tsType: 'string', operators: ['equality'] },
|
|
67
|
-
{
|
|
109
|
+
{
|
|
110
|
+
name: 'InternetAddressFilter',
|
|
111
|
+
tsType: 'string',
|
|
112
|
+
operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'],
|
|
113
|
+
},
|
|
68
114
|
{ name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
|
|
69
115
|
];
|
|
70
116
|
/**
|
|
@@ -224,7 +270,15 @@ function getRelatedTypeName(tableName, tableByName) {
|
|
|
224
270
|
}
|
|
225
271
|
function getRelatedOrderByName(tableName, tableByName) {
|
|
226
272
|
const relatedTable = tableByName.get(tableName);
|
|
227
|
-
|
|
273
|
+
if (relatedTable) {
|
|
274
|
+
return getOrderByTypeName(relatedTable);
|
|
275
|
+
}
|
|
276
|
+
// For ManyToMany connection types, don't pluralize - just append OrderBy
|
|
277
|
+
// These types already have a fixed suffix pattern like "UserUsersByFooManyToMany"
|
|
278
|
+
if (tableName.endsWith('ManyToMany')) {
|
|
279
|
+
return `${tableName}OrderBy`;
|
|
280
|
+
}
|
|
281
|
+
return `${pluralize(tableName)}OrderBy`;
|
|
228
282
|
}
|
|
229
283
|
function getRelatedFilterName(tableName, tableByName) {
|
|
230
284
|
const relatedTable = tableByName.get(tableName);
|
|
@@ -405,6 +459,39 @@ function addTableFilterTypes(sourceFile, tables) {
|
|
|
405
459
|
}
|
|
406
460
|
}
|
|
407
461
|
// ============================================================================
|
|
462
|
+
// Condition Types Generator (AST-based)
|
|
463
|
+
// ============================================================================
|
|
464
|
+
/**
|
|
465
|
+
* Build properties for a table condition interface
|
|
466
|
+
* Condition types are simpler than Filter types - they use direct value equality
|
|
467
|
+
*/
|
|
468
|
+
function buildTableConditionProperties(table) {
|
|
469
|
+
const properties = [];
|
|
470
|
+
for (const field of table.fields) {
|
|
471
|
+
const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
|
|
472
|
+
if (isRelationField(field.name, table))
|
|
473
|
+
continue;
|
|
474
|
+
// Condition types use the raw scalar type (nullable)
|
|
475
|
+
const tsType = scalarToTsType(fieldType, { unknownScalar: 'unknown' });
|
|
476
|
+
properties.push({
|
|
477
|
+
name: field.name,
|
|
478
|
+
type: `${tsType} | null`,
|
|
479
|
+
optional: true,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return properties;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Add table condition types
|
|
486
|
+
*/
|
|
487
|
+
function addTableConditionTypes(sourceFile, tables) {
|
|
488
|
+
addSectionComment(sourceFile, 'Table Condition Types');
|
|
489
|
+
for (const table of tables) {
|
|
490
|
+
const conditionName = getConditionTypeName(table);
|
|
491
|
+
sourceFile.addInterface(createInterface(conditionName, buildTableConditionProperties(table)));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// ============================================================================
|
|
408
495
|
// OrderBy Types Generator (AST-based)
|
|
409
496
|
// ============================================================================
|
|
410
497
|
/**
|
|
@@ -488,7 +575,11 @@ function buildPatchProperties(table) {
|
|
|
488
575
|
continue;
|
|
489
576
|
const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
|
|
490
577
|
const tsType = scalarToInputTs(fieldType);
|
|
491
|
-
properties.push({
|
|
578
|
+
properties.push({
|
|
579
|
+
name: field.name,
|
|
580
|
+
type: `${tsType} | null`,
|
|
581
|
+
optional: true,
|
|
582
|
+
});
|
|
492
583
|
}
|
|
493
584
|
return properties;
|
|
494
585
|
}
|
|
@@ -602,7 +693,9 @@ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrud
|
|
|
602
693
|
properties.push({ name: field.name, type: tsType, optional });
|
|
603
694
|
// Follow nested Input types
|
|
604
695
|
const baseType = getTypeBaseName(field.type);
|
|
605
|
-
if (baseType &&
|
|
696
|
+
if (baseType &&
|
|
697
|
+
baseType.endsWith('Input') &&
|
|
698
|
+
!generatedTypes.has(baseType)) {
|
|
606
699
|
typesToGenerate.add(baseType);
|
|
607
700
|
}
|
|
608
701
|
}
|
|
@@ -628,7 +721,8 @@ export function collectPayloadTypeNames(operations) {
|
|
|
628
721
|
const payloadTypes = new Set();
|
|
629
722
|
for (const op of operations) {
|
|
630
723
|
const baseName = getTypeBaseName(op.returnType);
|
|
631
|
-
if (baseName &&
|
|
724
|
+
if (baseName &&
|
|
725
|
+
(baseName.endsWith('Payload') || !baseName.endsWith('Connection'))) {
|
|
632
726
|
payloadTypes.add(baseName);
|
|
633
727
|
}
|
|
634
728
|
}
|
|
@@ -642,8 +736,21 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
|
|
|
642
736
|
const generatedTypes = new Set(alreadyGeneratedTypes);
|
|
643
737
|
const typesToGenerate = new Set(Array.from(usedPayloadTypes));
|
|
644
738
|
const skipTypes = new Set([
|
|
645
|
-
'String',
|
|
646
|
-
'
|
|
739
|
+
'String',
|
|
740
|
+
'Int',
|
|
741
|
+
'Float',
|
|
742
|
+
'Boolean',
|
|
743
|
+
'ID',
|
|
744
|
+
'UUID',
|
|
745
|
+
'Datetime',
|
|
746
|
+
'Date',
|
|
747
|
+
'Time',
|
|
748
|
+
'JSON',
|
|
749
|
+
'BigInt',
|
|
750
|
+
'BigFloat',
|
|
751
|
+
'Cursor',
|
|
752
|
+
'Query',
|
|
753
|
+
'Mutation',
|
|
647
754
|
]);
|
|
648
755
|
// Process all types - no artificial limit
|
|
649
756
|
while (typesToGenerate.size > 0) {
|
|
@@ -674,7 +781,9 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
|
|
|
674
781
|
optional: isNullable,
|
|
675
782
|
});
|
|
676
783
|
// Follow nested OBJECT types
|
|
677
|
-
if (baseType &&
|
|
784
|
+
if (baseType &&
|
|
785
|
+
!generatedTypes.has(baseType) &&
|
|
786
|
+
!skipTypes.has(baseType)) {
|
|
678
787
|
const nestedType = typeRegistry.get(baseType);
|
|
679
788
|
if (nestedType?.kind === 'OBJECT') {
|
|
680
789
|
typesToGenerate.add(baseType);
|
|
@@ -728,6 +837,8 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
|
|
|
728
837
|
addEntitySelectTypes(sourceFile, tables, tableByName);
|
|
729
838
|
// 4. Table filter types
|
|
730
839
|
addTableFilterTypes(sourceFile, tables);
|
|
840
|
+
// 4b. Table condition types (simple equality filter)
|
|
841
|
+
addTableConditionTypes(sourceFile, tables);
|
|
731
842
|
// 5. OrderBy types
|
|
732
843
|
addOrderByTypes(sourceFile, tables);
|
|
733
844
|
// 6. CRUD input types
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, } from '../ts-ast';
|
|
2
|
-
import { getTableNames, getOrderByTypeName, getFilterTypeName, lcFirst } from '../utils';
|
|
2
|
+
import { getTableNames, getOrderByTypeName, getFilterTypeName, lcFirst, } from '../utils';
|
|
3
3
|
/**
|
|
4
4
|
* Generate a model class file for a table
|
|
5
5
|
*/
|
|
@@ -120,7 +120,8 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
120
120
|
before: args?.before,
|
|
121
121
|
offset: args?.offset,
|
|
122
122
|
},
|
|
123
|
-
'${whereTypeName}'
|
|
123
|
+
'${whereTypeName}',
|
|
124
|
+
'${orderByTypeName}'
|
|
124
125
|
);
|
|
125
126
|
return new QueryBuilder({
|
|
126
127
|
client: this.client,
|
|
@@ -105,7 +105,7 @@ export declare function buildFindManyDocument(operationName: string, queryField:
|
|
|
105
105
|
after?: string;
|
|
106
106
|
before?: string;
|
|
107
107
|
offset?: number;
|
|
108
|
-
}, filterTypeName: string): {
|
|
108
|
+
}, filterTypeName: string, orderByTypeName: string): {
|
|
109
109
|
document: string;
|
|
110
110
|
variables: Record<string, unknown>;
|
|
111
111
|
};
|
|
@@ -164,7 +164,7 @@ export function buildSelections(select, fieldMeta) {
|
|
|
164
164
|
/**
|
|
165
165
|
* Build a findMany query document
|
|
166
166
|
*/
|
|
167
|
-
export function buildFindManyDocument(operationName, queryField, select, args, filterTypeName) {
|
|
167
|
+
export function buildFindManyDocument(operationName, queryField, select, args, filterTypeName, orderByTypeName) {
|
|
168
168
|
const selections = select ? buildSelections(select) : 'id';
|
|
169
169
|
// Build variable definitions and query arguments
|
|
170
170
|
const varDefs = [];
|
|
@@ -176,7 +176,7 @@ export function buildFindManyDocument(operationName, queryField, select, args, f
|
|
|
176
176
|
variables.where = args.where;
|
|
177
177
|
}
|
|
178
178
|
if (args.orderBy && args.orderBy.length > 0) {
|
|
179
|
-
varDefs.push(`$orderBy: [${
|
|
179
|
+
varDefs.push(`$orderBy: [${orderByTypeName}!]`);
|
|
180
180
|
queryArgs.push('orderBy: $orderBy');
|
|
181
181
|
variables.orderBy = args.orderBy;
|
|
182
182
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createProject, createSourceFile, getMinimalFormattedOutput, createFileHeader, createInterface, createTypeAlias, addSectionComment, } from './ts-ast';
|
|
2
2
|
import { getTypeBaseName } from './type-resolver';
|
|
3
|
-
import { scalarToTsType, SCALAR_NAMES, BASE_FILTER_TYPE_NAMES } from './scalars';
|
|
3
|
+
import { scalarToTsType, SCALAR_NAMES, BASE_FILTER_TYPE_NAMES, } from './scalars';
|
|
4
4
|
// ============================================================================
|
|
5
5
|
// Constants
|
|
6
6
|
// ============================================================================
|
|
@@ -26,13 +26,13 @@ const SKIP_TYPES = new Set([
|
|
|
26
26
|
/**
|
|
27
27
|
* Type name patterns to skip (regex patterns)
|
|
28
28
|
*
|
|
29
|
-
* Note: We intentionally DO NOT skip Connection, Edge, Filter,
|
|
30
|
-
* because they may be referenced by custom operations
|
|
31
|
-
*
|
|
29
|
+
* Note: We intentionally DO NOT skip Connection, Edge, Filter, Patch, Condition,
|
|
30
|
+
* or OrderBy types because they may be referenced by custom operations.
|
|
31
|
+
* Previously Condition and OrderBy were skipped but they ARE needed for
|
|
32
|
+
* custom queries like `schemata`, `apiSchemata`, etc.
|
|
32
33
|
*/
|
|
33
34
|
const SKIP_TYPE_PATTERNS = [
|
|
34
|
-
|
|
35
|
-
/OrderBy$/, // e.g., UsersOrderBy (ordering is handled separately)
|
|
35
|
+
// Currently no patterns are skipped - all types may be needed by custom operations
|
|
36
36
|
];
|
|
37
37
|
// ============================================================================
|
|
38
38
|
// Type Conversion Utilities
|
|
@@ -101,9 +101,14 @@ export declare function getDeleteMutationName(table: CleanTable): string;
|
|
|
101
101
|
export declare function getFilterTypeName(table: CleanTable): string;
|
|
102
102
|
/**
|
|
103
103
|
* Get PostGraphile OrderBy enum type name
|
|
104
|
-
* e.g., "CarsOrderBy"
|
|
104
|
+
* e.g., "CarsOrderBy", "AddressesOrderBy"
|
|
105
105
|
*/
|
|
106
106
|
export declare function getOrderByTypeName(table: CleanTable): string;
|
|
107
|
+
/**
|
|
108
|
+
* Get PostGraphile Condition type name (simple equality filter)
|
|
109
|
+
* e.g., "CarCondition", "AddressCondition"
|
|
110
|
+
*/
|
|
111
|
+
export declare function getConditionTypeName(table: CleanTable): string;
|
|
107
112
|
/**
|
|
108
113
|
* Get PostGraphile create input type name
|
|
109
114
|
* e.g., "CreateCarInput"
|
package/esm/cli/codegen/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { scalarToTsType, scalarToFilterType } from './scalars';
|
|
2
|
+
import { pluralize } from '../introspect/pluralize';
|
|
2
3
|
// ============================================================================
|
|
3
4
|
// String manipulation
|
|
4
5
|
// ============================================================================
|
|
@@ -36,7 +37,9 @@ export function toScreamingSnake(str) {
|
|
|
36
37
|
export function getTableNames(table) {
|
|
37
38
|
const typeName = table.name;
|
|
38
39
|
const singularName = table.inflection?.tableFieldName || lcFirst(typeName);
|
|
39
|
-
const pluralName = table.query?.all ||
|
|
40
|
+
const pluralName = table.query?.all ||
|
|
41
|
+
table.inflection?.allRows ||
|
|
42
|
+
lcFirst(pluralize(typeName));
|
|
40
43
|
const pluralTypeName = ucFirst(pluralName);
|
|
41
44
|
return {
|
|
42
45
|
typeName,
|
|
@@ -125,13 +128,15 @@ export function getDeleteMutationFileName(table) {
|
|
|
125
128
|
* Uses inflection from _meta, falls back to convention
|
|
126
129
|
*/
|
|
127
130
|
export function getAllRowsQueryName(table) {
|
|
128
|
-
return table.query?.all ||
|
|
131
|
+
return (table.query?.all ||
|
|
132
|
+
table.inflection?.allRows ||
|
|
133
|
+
lcFirst(pluralize(table.name)));
|
|
129
134
|
}
|
|
130
135
|
/**
|
|
131
136
|
* Get the GraphQL query name for fetching single row
|
|
132
137
|
*/
|
|
133
138
|
export function getSingleRowQueryName(table) {
|
|
134
|
-
return table.query?.one || table.inflection?.tableFieldName || lcFirst(table.name);
|
|
139
|
+
return (table.query?.one || table.inflection?.tableFieldName || lcFirst(table.name));
|
|
135
140
|
}
|
|
136
141
|
/**
|
|
137
142
|
* Get the GraphQL mutation name for creating
|
|
@@ -163,10 +168,17 @@ export function getFilterTypeName(table) {
|
|
|
163
168
|
}
|
|
164
169
|
/**
|
|
165
170
|
* Get PostGraphile OrderBy enum type name
|
|
166
|
-
* e.g., "CarsOrderBy"
|
|
171
|
+
* e.g., "CarsOrderBy", "AddressesOrderBy"
|
|
167
172
|
*/
|
|
168
173
|
export function getOrderByTypeName(table) {
|
|
169
|
-
return table.inflection?.orderByType || `${table.name}
|
|
174
|
+
return table.inflection?.orderByType || `${pluralize(table.name)}OrderBy`;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get PostGraphile Condition type name (simple equality filter)
|
|
178
|
+
* e.g., "CarCondition", "AddressCondition"
|
|
179
|
+
*/
|
|
180
|
+
export function getConditionTypeName(table) {
|
|
181
|
+
return table.inflection?.conditionType || `${table.name}Condition`;
|
|
170
182
|
}
|
|
171
183
|
/**
|
|
172
184
|
* Get PostGraphile create input type name
|
|
@@ -254,13 +266,15 @@ export function getPrimaryKeyInfo(table) {
|
|
|
254
266
|
const pk = table.constraints?.primaryKey?.[0];
|
|
255
267
|
if (!pk || pk.fields.length === 0) {
|
|
256
268
|
// Fallback: try to find 'id' field in table fields
|
|
257
|
-
const idField = table.fields.find(f => f.name.toLowerCase() === 'id');
|
|
269
|
+
const idField = table.fields.find((f) => f.name.toLowerCase() === 'id');
|
|
258
270
|
if (idField) {
|
|
259
|
-
return [
|
|
271
|
+
return [
|
|
272
|
+
{
|
|
260
273
|
name: idField.name,
|
|
261
274
|
gqlType: idField.type.gqlType,
|
|
262
275
|
tsType: fieldTypeToTs(idField.type),
|
|
263
|
-
}
|
|
276
|
+
},
|
|
277
|
+
];
|
|
264
278
|
}
|
|
265
279
|
// Last resort: assume 'id' of type string (UUID)
|
|
266
280
|
return [{ name: 'id', gqlType: 'UUID', tsType: 'string' }];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate ORM command - generates Prisma-like ORM client
|
|
2
|
+
* Generate ORM command - generates Prisma-like ORM client from GraphQL schema
|
|
3
3
|
*
|
|
4
4
|
* This command:
|
|
5
|
-
* 1. Fetches
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. Fetches schema from endpoint or loads from file
|
|
6
|
+
* 2. Infers table metadata from introspection (replaces _meta)
|
|
7
7
|
* 3. Generates a Prisma-like ORM client with fluent API
|
|
8
8
|
*/
|
|
9
9
|
export interface GenerateOrmOptions {
|
|
@@ -11,6 +11,8 @@ export interface GenerateOrmOptions {
|
|
|
11
11
|
config?: string;
|
|
12
12
|
/** GraphQL endpoint URL (overrides config) */
|
|
13
13
|
endpoint?: string;
|
|
14
|
+
/** Path to GraphQL schema file (.graphql) */
|
|
15
|
+
schema?: string;
|
|
14
16
|
/** Output directory (overrides config) */
|
|
15
17
|
output?: string;
|
|
16
18
|
/** Authorization header */
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate ORM command - generates Prisma-like ORM client
|
|
2
|
+
* Generate ORM command - generates Prisma-like ORM client from GraphQL schema
|
|
3
3
|
*
|
|
4
4
|
* This command:
|
|
5
|
-
* 1. Fetches
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. Fetches schema from endpoint or loads from file
|
|
6
|
+
* 2. Infers table metadata from introspection (replaces _meta)
|
|
7
7
|
* 3. Generates a Prisma-like ORM client with fluent API
|
|
8
8
|
*/
|
|
9
9
|
import { resolveConfig } from '../../types/config';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { transformMetaToCleanTables, filterTables, } from '../introspect/transform';
|
|
13
|
-
import { transformSchemaToOperations, filterOperations, getTableOperationNames, getCustomOperations, } from '../introspect/transform-schema';
|
|
10
|
+
import { createSchemaSource, validateSourceOptions, } from '../introspect/source';
|
|
11
|
+
import { runCodegenPipeline, validateTablesFound } from './shared';
|
|
14
12
|
import { findConfigFile, loadConfigFile } from './init';
|
|
15
13
|
import { writeGeneratedFiles } from './generate';
|
|
16
14
|
import { generateOrm } from '../codegen/orm';
|
|
@@ -31,109 +29,90 @@ export async function generateOrmCommand(options = {}) {
|
|
|
31
29
|
const config = configResult.config;
|
|
32
30
|
// Use ORM output directory if specified, otherwise default
|
|
33
31
|
const outputDir = options.output || config.orm?.output || './generated/orm';
|
|
34
|
-
|
|
32
|
+
// Log source
|
|
33
|
+
if (config.schema) {
|
|
34
|
+
log(` Schema: ${config.schema}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
log(` Endpoint: ${config.endpoint}`);
|
|
38
|
+
}
|
|
35
39
|
log(` Output: ${outputDir}`);
|
|
36
|
-
// 2.
|
|
37
|
-
const
|
|
38
|
-
|
|
40
|
+
// 2. Create schema source
|
|
41
|
+
const sourceValidation = validateSourceOptions({
|
|
42
|
+
endpoint: config.endpoint || undefined,
|
|
43
|
+
schema: config.schema || undefined,
|
|
44
|
+
});
|
|
45
|
+
if (!sourceValidation.valid) {
|
|
39
46
|
return {
|
|
40
47
|
success: false,
|
|
41
|
-
message:
|
|
48
|
+
message: sourceValidation.error,
|
|
42
49
|
};
|
|
43
50
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const metaResult = await fetchMeta({
|
|
49
|
-
endpoint: config.endpoint,
|
|
50
|
-
authorization: authHeader,
|
|
51
|
+
const source = createSchemaSource({
|
|
52
|
+
endpoint: config.endpoint || undefined,
|
|
53
|
+
schema: config.schema || undefined,
|
|
54
|
+
authorization: options.authorization || config.headers['Authorization'],
|
|
51
55
|
headers: config.headers,
|
|
52
|
-
timeout: 30000,
|
|
53
56
|
});
|
|
54
|
-
|
|
57
|
+
// 3. Run the codegen pipeline
|
|
58
|
+
let pipelineResult;
|
|
59
|
+
try {
|
|
60
|
+
pipelineResult = await runCodegenPipeline({
|
|
61
|
+
source,
|
|
62
|
+
config,
|
|
63
|
+
verbose: options.verbose,
|
|
64
|
+
skipCustomOperations: options.skipCustomOperations,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
55
68
|
return {
|
|
56
69
|
success: false,
|
|
57
|
-
message: `Failed to fetch
|
|
70
|
+
message: `Failed to fetch schema: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
58
71
|
};
|
|
59
72
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// 5. Filter tables
|
|
65
|
-
tables = filterTables(tables, config.tables.include, config.tables.exclude);
|
|
66
|
-
log(` After filtering: ${tables.length} tables`);
|
|
67
|
-
if (tables.length === 0) {
|
|
73
|
+
const { tables, customOperations, stats } = pipelineResult;
|
|
74
|
+
// 4. Validate tables found
|
|
75
|
+
const tablesValidation = validateTablesFound(tables);
|
|
76
|
+
if (!tablesValidation.valid) {
|
|
68
77
|
return {
|
|
69
78
|
success: false,
|
|
70
|
-
message:
|
|
79
|
+
message: tablesValidation.error,
|
|
71
80
|
};
|
|
72
81
|
}
|
|
73
|
-
//
|
|
74
|
-
const tableOperationNames = getTableOperationNames(tables);
|
|
75
|
-
// 6. Fetch __schema for custom operations (unless skipped)
|
|
76
|
-
let customQueries = [];
|
|
77
|
-
let customMutations = [];
|
|
78
|
-
let customOperationsData;
|
|
79
|
-
if (!options.skipCustomOperations) {
|
|
80
|
-
log('Fetching schema introspection (__schema)...');
|
|
81
|
-
const schemaResult = await fetchSchema({
|
|
82
|
-
endpoint: config.endpoint,
|
|
83
|
-
authorization: authHeader,
|
|
84
|
-
headers: config.headers,
|
|
85
|
-
timeout: 30000,
|
|
86
|
-
});
|
|
87
|
-
if (schemaResult.success && schemaResult.data) {
|
|
88
|
-
log('Transforming custom operations...');
|
|
89
|
-
// Transform to CleanOperation[]
|
|
90
|
-
const { queries: allQueries, mutations: allMutations, typeRegistry } = transformSchemaToOperations(schemaResult.data);
|
|
91
|
-
log(` Found ${allQueries.length} queries and ${allMutations.length} mutations total`);
|
|
92
|
-
// Filter by config include/exclude
|
|
93
|
-
const filteredQueries = filterOperations(allQueries, config.queries.include, config.queries.exclude);
|
|
94
|
-
const filteredMutations = filterOperations(allMutations, config.mutations.include, config.mutations.exclude);
|
|
95
|
-
log(` After config filtering: ${filteredQueries.length} queries, ${filteredMutations.length} mutations`);
|
|
96
|
-
// Remove table operations (already handled by table generators)
|
|
97
|
-
const customQueriesOps = getCustomOperations(filteredQueries, tableOperationNames);
|
|
98
|
-
const customMutationsOps = getCustomOperations(filteredMutations, tableOperationNames);
|
|
99
|
-
log(` Custom operations: ${customQueriesOps.length} queries, ${customMutationsOps.length} mutations`);
|
|
100
|
-
customQueries = customQueriesOps.map((q) => q.name);
|
|
101
|
-
customMutations = customMutationsOps.map((m) => m.name);
|
|
102
|
-
customOperationsData = {
|
|
103
|
-
queries: customQueriesOps,
|
|
104
|
-
mutations: customMutationsOps,
|
|
105
|
-
typeRegistry,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
log(` Warning: Could not fetch __schema: ${schemaResult.error}`);
|
|
110
|
-
log(' Continuing with table-only generation...');
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// 7. Generate ORM code
|
|
82
|
+
// 5. Generate ORM code
|
|
114
83
|
console.log('Generating code...');
|
|
115
|
-
const { files: generatedFiles, stats } = generateOrm({
|
|
84
|
+
const { files: generatedFiles, stats: genStats } = generateOrm({
|
|
116
85
|
tables,
|
|
117
|
-
customOperations:
|
|
86
|
+
customOperations: {
|
|
87
|
+
queries: customOperations.queries,
|
|
88
|
+
mutations: customOperations.mutations,
|
|
89
|
+
typeRegistry: customOperations.typeRegistry,
|
|
90
|
+
},
|
|
118
91
|
config,
|
|
119
92
|
});
|
|
120
|
-
console.log(`Generated ${
|
|
121
|
-
log(` ${
|
|
122
|
-
log(` ${
|
|
123
|
-
log(` ${
|
|
93
|
+
console.log(`Generated ${genStats.totalFiles} files`);
|
|
94
|
+
log(` ${genStats.tables} table models`);
|
|
95
|
+
log(` ${genStats.customQueries} custom query operations`);
|
|
96
|
+
log(` ${genStats.customMutations} custom mutation operations`);
|
|
97
|
+
const customQueries = customOperations.queries.map((q) => q.name);
|
|
98
|
+
const customMutations = customOperations.mutations.map((m) => m.name);
|
|
124
99
|
if (options.dryRun) {
|
|
125
100
|
return {
|
|
126
101
|
success: true,
|
|
127
|
-
message: `Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${customQueries
|
|
102
|
+
message: `Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${stats.customQueries + stats.customMutations} custom operations.`,
|
|
128
103
|
tables: tables.map((t) => t.name),
|
|
129
104
|
customQueries,
|
|
130
105
|
customMutations,
|
|
131
106
|
filesWritten: generatedFiles.map((f) => f.path),
|
|
132
107
|
};
|
|
133
108
|
}
|
|
134
|
-
//
|
|
109
|
+
// 6. Write files
|
|
135
110
|
log('Writing files...');
|
|
136
|
-
const writeResult = await writeGeneratedFiles(generatedFiles, outputDir, [
|
|
111
|
+
const writeResult = await writeGeneratedFiles(generatedFiles, outputDir, [
|
|
112
|
+
'models',
|
|
113
|
+
'query',
|
|
114
|
+
'mutation',
|
|
115
|
+
]);
|
|
137
116
|
if (!writeResult.success) {
|
|
138
117
|
return {
|
|
139
118
|
success: false,
|
|
@@ -168,7 +147,8 @@ async function loadConfig(options) {
|
|
|
168
147
|
}
|
|
169
148
|
// Override with CLI options
|
|
170
149
|
const mergedConfig = {
|
|
171
|
-
endpoint: options.endpoint || baseConfig.endpoint
|
|
150
|
+
endpoint: options.endpoint || baseConfig.endpoint,
|
|
151
|
+
schema: options.schema || baseConfig.schema,
|
|
172
152
|
output: options.output || baseConfig.output,
|
|
173
153
|
headers: baseConfig.headers,
|
|
174
154
|
tables: baseConfig.tables,
|
|
@@ -180,10 +160,11 @@ async function loadConfig(options) {
|
|
|
180
160
|
codegen: baseConfig.codegen,
|
|
181
161
|
orm: baseConfig.orm,
|
|
182
162
|
};
|
|
183
|
-
|
|
163
|
+
// Validate at least one source is provided
|
|
164
|
+
if (!mergedConfig.endpoint && !mergedConfig.schema) {
|
|
184
165
|
return {
|
|
185
166
|
success: false,
|
|
186
|
-
error: 'No
|
|
167
|
+
error: 'No source specified. Use --endpoint or --schema, or create a config file with "graphql-codegen init".',
|
|
187
168
|
};
|
|
188
169
|
}
|
|
189
170
|
// Resolve with defaults
|
|
@@ -3,6 +3,8 @@ export interface GenerateOptions {
|
|
|
3
3
|
config?: string;
|
|
4
4
|
/** GraphQL endpoint URL (overrides config) */
|
|
5
5
|
endpoint?: string;
|
|
6
|
+
/** Path to GraphQL schema file (.graphql) */
|
|
7
|
+
schema?: string;
|
|
6
8
|
/** Output directory (overrides config) */
|
|
7
9
|
output?: string;
|
|
8
10
|
/** Authorization header */
|