@constructive-io/graphql-codegen 2.20.1 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/cli/codegen/barrel.d.ts +4 -1
- package/cli/codegen/barrel.js +18 -12
- package/cli/codegen/client.js +33 -0
- package/cli/codegen/custom-mutations.d.ts +11 -1
- package/cli/codegen/custom-mutations.js +49 -15
- package/cli/codegen/custom-queries.d.ts +8 -0
- package/cli/codegen/custom-queries.js +82 -47
- package/cli/codegen/gql-ast.js +9 -5
- package/cli/codegen/index.js +39 -8
- package/cli/codegen/mutations.d.ts +14 -4
- package/cli/codegen/mutations.js +114 -28
- package/cli/codegen/orm/barrel.js +4 -2
- package/cli/codegen/orm/index.js +17 -0
- package/cli/codegen/orm/input-types-generator.js +83 -29
- package/cli/codegen/orm/model-generator.js +6 -4
- package/cli/codegen/queries.d.ts +7 -3
- package/cli/codegen/queries.js +185 -158
- package/cli/codegen/scalars.d.ts +6 -4
- package/cli/codegen/scalars.js +17 -9
- package/cli/codegen/schema-types-generator.d.ts +26 -0
- package/cli/codegen/schema-types-generator.js +365 -0
- package/cli/codegen/ts-ast.d.ts +3 -1
- package/cli/codegen/ts-ast.js +2 -2
- package/cli/codegen/type-resolver.d.ts +52 -6
- package/cli/codegen/type-resolver.js +97 -19
- package/cli/codegen/types.d.ts +7 -4
- package/cli/codegen/types.js +94 -41
- package/cli/codegen/utils.d.ts +20 -2
- package/cli/codegen/utils.js +32 -7
- package/cli/commands/generate-orm.js +5 -5
- package/cli/commands/generate.d.ts +4 -1
- package/cli/commands/generate.js +27 -8
- package/cli/introspect/transform-schema.d.ts +33 -21
- package/cli/introspect/transform-schema.js +31 -21
- package/esm/cli/codegen/barrel.d.ts +4 -1
- package/esm/cli/codegen/barrel.js +18 -12
- package/esm/cli/codegen/client.js +33 -0
- package/esm/cli/codegen/custom-mutations.d.ts +11 -1
- package/esm/cli/codegen/custom-mutations.js +50 -16
- package/esm/cli/codegen/custom-queries.d.ts +8 -0
- package/esm/cli/codegen/custom-queries.js +83 -48
- package/esm/cli/codegen/gql-ast.js +10 -6
- package/esm/cli/codegen/index.js +39 -8
- package/esm/cli/codegen/mutations.d.ts +14 -4
- package/esm/cli/codegen/mutations.js +115 -29
- package/esm/cli/codegen/orm/barrel.js +4 -2
- package/esm/cli/codegen/orm/index.js +17 -0
- package/esm/cli/codegen/orm/input-types-generator.js +83 -29
- package/esm/cli/codegen/orm/model-generator.js +7 -5
- package/esm/cli/codegen/queries.d.ts +7 -3
- package/esm/cli/codegen/queries.js +186 -159
- package/esm/cli/codegen/scalars.d.ts +6 -4
- package/esm/cli/codegen/scalars.js +16 -8
- package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
- package/esm/cli/codegen/schema-types-generator.js +362 -0
- package/esm/cli/codegen/ts-ast.d.ts +3 -1
- package/esm/cli/codegen/ts-ast.js +2 -2
- package/esm/cli/codegen/type-resolver.d.ts +52 -6
- package/esm/cli/codegen/type-resolver.js +97 -20
- package/esm/cli/codegen/types.d.ts +7 -4
- package/esm/cli/codegen/types.js +95 -41
- package/esm/cli/codegen/utils.d.ts +20 -2
- package/esm/cli/codegen/utils.js +31 -7
- package/esm/cli/commands/generate-orm.js +5 -5
- package/esm/cli/commands/generate.d.ts +4 -1
- package/esm/cli/commands/generate.js +27 -8
- package/esm/cli/introspect/transform-schema.d.ts +33 -21
- package/esm/cli/introspect/transform-schema.js +31 -21
- package/esm/types/config.d.ts +16 -1
- package/esm/types/config.js +6 -0
- package/esm/types/schema.d.ts +2 -0
- package/package.json +8 -6
- package/types/config.d.ts +16 -1
- package/types/config.js +6 -0
- package/types/schema.d.ts +2 -0
- package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
- package/__tests__/codegen/input-types-generator.test.js +0 -635
- package/cli/codegen/filters.d.ts +0 -27
- package/cli/codegen/filters.js +0 -357
- package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
- package/cli/codegen/orm/input-types-generator.test.js +0 -75
- package/cli/codegen/orm/select-types.test.d.ts +0 -11
- package/cli/codegen/orm/select-types.test.js +0 -22
- package/cli/introspect/transform-schema.test.d.ts +0 -1
- package/cli/introspect/transform-schema.test.js +0 -67
- package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
- package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
- package/esm/cli/codegen/filters.d.ts +0 -27
- package/esm/cli/codegen/filters.js +0 -351
- package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
- package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
- package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
- package/esm/cli/codegen/orm/select-types.test.js +0 -21
- package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
- package/esm/cli/introspect/transform-schema.test.js +0 -65
package/cli/codegen/types.d.ts
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { CleanTable } from '../../types/schema';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Options for generating types.ts
|
|
7
7
|
*/
|
|
8
|
-
export
|
|
8
|
+
export interface GenerateTypesOptions {
|
|
9
|
+
/** Enum type names that are available from schema-types.ts */
|
|
10
|
+
enumsFromSchemaTypes?: string[];
|
|
11
|
+
}
|
|
9
12
|
/**
|
|
10
|
-
* Generate
|
|
13
|
+
* Generate types.ts content with all entity interfaces and base filter types
|
|
11
14
|
*/
|
|
12
|
-
export declare function
|
|
15
|
+
export declare function generateTypesFile(tables: CleanTable[], options?: GenerateTypesOptions): string;
|
package/cli/codegen/types.js
CHANGED
|
@@ -1,19 +1,103 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateTypesFile = generateTypesFile;
|
|
4
|
-
exports.generateMinimalEntityType = generateMinimalEntityType;
|
|
5
4
|
const ts_ast_1 = require("./ts-ast");
|
|
6
5
|
const utils_1 = require("./utils");
|
|
7
|
-
|
|
6
|
+
/** All filter type configurations - scalar and list filters */
|
|
7
|
+
const FILTER_CONFIGS = [
|
|
8
|
+
// Scalar filters
|
|
9
|
+
{ name: 'StringFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'string'] },
|
|
10
|
+
{ name: 'IntFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
11
|
+
{ name: 'FloatFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
12
|
+
{ name: 'BooleanFilter', tsType: 'boolean', operators: ['equality'] },
|
|
13
|
+
{ name: 'UUIDFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray'] },
|
|
14
|
+
{ name: 'DatetimeFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
15
|
+
{ name: 'DateFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
16
|
+
{ name: 'JSONFilter', tsType: 'Record<string, unknown>', operators: ['equality', 'distinct', 'json'] },
|
|
17
|
+
{ name: 'BigIntFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
18
|
+
{ name: 'BigFloatFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
19
|
+
{ name: 'BitStringFilter', tsType: 'string', operators: ['equality'] },
|
|
20
|
+
{ name: 'InternetAddressFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'] },
|
|
21
|
+
{ name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
|
|
22
|
+
// List filters
|
|
23
|
+
{ name: 'StringListFilter', tsType: 'string[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
|
|
24
|
+
{ name: 'IntListFilter', tsType: 'number[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
|
|
25
|
+
{ name: 'UUIDListFilter', tsType: 'string[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
|
|
26
|
+
];
|
|
27
|
+
/** Build filter properties based on operator sets */
|
|
28
|
+
function buildFilterProperties(tsType, operators) {
|
|
29
|
+
const props = [];
|
|
30
|
+
// Equality operators (isNull, equalTo, notEqualTo)
|
|
31
|
+
if (operators.includes('equality')) {
|
|
32
|
+
props.push({ name: 'isNull', type: 'boolean', optional: true }, { name: 'equalTo', type: tsType, optional: true }, { name: 'notEqualTo', type: tsType, optional: true });
|
|
33
|
+
}
|
|
34
|
+
// Distinct operators
|
|
35
|
+
if (operators.includes('distinct')) {
|
|
36
|
+
props.push({ name: 'distinctFrom', type: tsType, optional: true }, { name: 'notDistinctFrom', type: tsType, optional: true });
|
|
37
|
+
}
|
|
38
|
+
// In array operators
|
|
39
|
+
if (operators.includes('inArray')) {
|
|
40
|
+
props.push({ name: 'in', type: `${tsType}[]`, optional: true }, { name: 'notIn', type: `${tsType}[]`, optional: true });
|
|
41
|
+
}
|
|
42
|
+
// Comparison operators (lt, lte, gt, gte)
|
|
43
|
+
if (operators.includes('comparison')) {
|
|
44
|
+
props.push({ name: 'lessThan', type: tsType, optional: true }, { name: 'lessThanOrEqualTo', type: tsType, optional: true }, { name: 'greaterThan', type: tsType, optional: true }, { name: 'greaterThanOrEqualTo', type: tsType, optional: true });
|
|
45
|
+
}
|
|
46
|
+
// String-specific operators
|
|
47
|
+
if (operators.includes('string')) {
|
|
48
|
+
props.push({ name: 'includes', type: 'string', optional: true }, { name: 'notIncludes', type: 'string', optional: true }, { name: 'includesInsensitive', type: 'string', optional: true }, { name: 'notIncludesInsensitive', type: 'string', optional: true }, { name: 'startsWith', type: 'string', optional: true }, { name: 'notStartsWith', type: 'string', optional: true }, { name: 'startsWithInsensitive', type: 'string', optional: true }, { name: 'notStartsWithInsensitive', type: 'string', optional: true }, { name: 'endsWith', type: 'string', optional: true }, { name: 'notEndsWith', type: 'string', optional: true }, { name: 'endsWithInsensitive', type: 'string', optional: true }, { name: 'notEndsWithInsensitive', type: 'string', optional: true }, { name: 'like', type: 'string', optional: true }, { name: 'notLike', type: 'string', optional: true }, { name: 'likeInsensitive', type: 'string', optional: true }, { name: 'notLikeInsensitive', type: 'string', optional: true });
|
|
49
|
+
}
|
|
50
|
+
// JSON-specific operators
|
|
51
|
+
if (operators.includes('json')) {
|
|
52
|
+
props.push({ name: 'contains', type: 'unknown', optional: true }, { name: 'containedBy', type: 'unknown', optional: true }, { name: 'containsKey', type: 'string', optional: true }, { name: 'containsAllKeys', type: 'string[]', optional: true }, { name: 'containsAnyKeys', type: 'string[]', optional: true });
|
|
53
|
+
}
|
|
54
|
+
// Internet address operators
|
|
55
|
+
if (operators.includes('inet')) {
|
|
56
|
+
props.push({ name: 'contains', type: 'string', optional: true }, { name: 'containedBy', type: 'string', optional: true }, { name: 'containsOrContainedBy', type: 'string', optional: true });
|
|
57
|
+
}
|
|
58
|
+
// Full-text search operator
|
|
59
|
+
if (operators.includes('fulltext')) {
|
|
60
|
+
props.push({ name: 'matches', type: 'string', optional: true });
|
|
61
|
+
}
|
|
62
|
+
// List/Array operators (for StringListFilter, IntListFilter, etc.)
|
|
63
|
+
if (operators.includes('listArray')) {
|
|
64
|
+
// Extract base type from array type (e.g., 'string[]' -> 'string')
|
|
65
|
+
const baseType = tsType.replace('[]', '');
|
|
66
|
+
props.push({ name: 'contains', type: tsType, optional: true }, { name: 'containedBy', type: tsType, optional: true }, { name: 'overlaps', type: tsType, optional: true }, { name: 'anyEqualTo', type: baseType, optional: true }, { name: 'anyNotEqualTo', type: baseType, optional: true }, { name: 'anyLessThan', type: baseType, optional: true }, { name: 'anyLessThanOrEqualTo', type: baseType, optional: true }, { name: 'anyGreaterThan', type: baseType, optional: true }, { name: 'anyGreaterThanOrEqualTo', type: baseType, optional: true });
|
|
67
|
+
}
|
|
68
|
+
return props;
|
|
69
|
+
}
|
|
8
70
|
/**
|
|
9
71
|
* Generate types.ts content with all entity interfaces and base filter types
|
|
10
72
|
*/
|
|
11
|
-
function generateTypesFile(tables) {
|
|
73
|
+
function generateTypesFile(tables, options = {}) {
|
|
74
|
+
const { enumsFromSchemaTypes = [] } = options;
|
|
75
|
+
const enumSet = new Set(enumsFromSchemaTypes);
|
|
12
76
|
const project = (0, ts_ast_1.createProject)();
|
|
13
77
|
const sourceFile = (0, ts_ast_1.createSourceFile)(project, 'types.ts');
|
|
14
78
|
// Add file header
|
|
15
79
|
sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)('Entity types and filter types') + '\n\n');
|
|
16
|
-
//
|
|
80
|
+
// Collect which enums are actually used by entity fields
|
|
81
|
+
const usedEnums = new Set();
|
|
82
|
+
for (const table of tables) {
|
|
83
|
+
const scalarFields = (0, utils_1.getScalarFields)(table);
|
|
84
|
+
for (const field of scalarFields) {
|
|
85
|
+
// Check if the field's gqlType matches any known enum
|
|
86
|
+
const cleanType = field.type.gqlType.replace(/!/g, '');
|
|
87
|
+
if (enumSet.has(cleanType)) {
|
|
88
|
+
usedEnums.add(cleanType);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Add import for enum types from schema-types if any are used
|
|
93
|
+
if (usedEnums.size > 0) {
|
|
94
|
+
sourceFile.addImportDeclaration((0, ts_ast_1.createImport)({
|
|
95
|
+
moduleSpecifier: './schema-types',
|
|
96
|
+
typeOnlyNamedImports: Array.from(usedEnums).sort(),
|
|
97
|
+
}));
|
|
98
|
+
sourceFile.addStatements('');
|
|
99
|
+
}
|
|
100
|
+
// Add section comment for entity types
|
|
17
101
|
sourceFile.addStatements('// ============================================================================');
|
|
18
102
|
sourceFile.addStatements('// Entity types');
|
|
19
103
|
sourceFile.addStatements('// ============================================================================\n');
|
|
@@ -26,44 +110,13 @@ function generateTypesFile(tables) {
|
|
|
26
110
|
}));
|
|
27
111
|
sourceFile.addInterface((0, ts_ast_1.createInterface)(table.name, properties));
|
|
28
112
|
}
|
|
29
|
-
// Add section comment for
|
|
113
|
+
// Add section comment for filter types
|
|
30
114
|
sourceFile.addStatements('\n// ============================================================================');
|
|
31
|
-
sourceFile.addStatements('// Filter types (shared)');
|
|
115
|
+
sourceFile.addStatements('// Filter types (shared PostGraphile filter interfaces)');
|
|
32
116
|
sourceFile.addStatements('// ============================================================================\n');
|
|
33
|
-
//
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
.split('\n')
|
|
38
|
-
.slice(6) // Skip header lines
|
|
39
|
-
.join('\n');
|
|
40
|
-
sourceFile.addStatements(filterInterfaces);
|
|
41
|
-
return (0, ts_ast_1.getFormattedOutput)(sourceFile);
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Generate a minimal entity type (just id and display fields)
|
|
45
|
-
*/
|
|
46
|
-
function generateMinimalEntityType(table) {
|
|
47
|
-
const project = (0, ts_ast_1.createProject)();
|
|
48
|
-
const sourceFile = (0, ts_ast_1.createSourceFile)(project, 'minimal.ts');
|
|
49
|
-
const scalarFields = (0, utils_1.getScalarFields)(table);
|
|
50
|
-
// Find id and likely display fields
|
|
51
|
-
const displayFields = scalarFields.filter((f) => {
|
|
52
|
-
const name = f.name.toLowerCase();
|
|
53
|
-
return (name === 'id' ||
|
|
54
|
-
name === 'name' ||
|
|
55
|
-
name === 'title' ||
|
|
56
|
-
name === 'label' ||
|
|
57
|
-
name === 'email' ||
|
|
58
|
-
name.endsWith('name') ||
|
|
59
|
-
name.endsWith('title'));
|
|
60
|
-
});
|
|
61
|
-
// If no display fields found, take first 5 scalar fields
|
|
62
|
-
const fieldsToUse = displayFields.length > 0 ? displayFields : scalarFields.slice(0, 5);
|
|
63
|
-
const properties = fieldsToUse.map((field) => ({
|
|
64
|
-
name: field.name,
|
|
65
|
-
type: `${(0, utils_1.fieldTypeToTs)(field.type)} | null`,
|
|
66
|
-
}));
|
|
67
|
-
sourceFile.addInterface((0, ts_ast_1.createInterface)(`${table.name}Minimal`, properties));
|
|
117
|
+
// Generate all filter types
|
|
118
|
+
for (const { name, tsType, operators } of FILTER_CONFIGS) {
|
|
119
|
+
sourceFile.addInterface((0, ts_ast_1.createInterface)(name, buildFilterProperties(tsType, operators)));
|
|
120
|
+
}
|
|
68
121
|
return (0, ts_ast_1.getFormattedOutput)(sourceFile);
|
|
69
122
|
}
|
package/cli/codegen/utils.d.ts
CHANGED
|
@@ -134,8 +134,10 @@ export declare function gqlTypeToTs(gqlType: string, isArray?: boolean): string;
|
|
|
134
134
|
export declare function fieldTypeToTs(fieldType: CleanFieldType): string;
|
|
135
135
|
/**
|
|
136
136
|
* Get the PostGraphile filter type for a GraphQL scalar
|
|
137
|
+
* @param gqlType - The GraphQL type string (e.g., "String", "UUID")
|
|
138
|
+
* @param isArray - Whether this is an array type
|
|
137
139
|
*/
|
|
138
|
-
export declare function getScalarFilterType(gqlType: string): string | null;
|
|
140
|
+
export declare function getScalarFilterType(gqlType: string, isArray?: boolean): string | null;
|
|
139
141
|
/**
|
|
140
142
|
* Check if a field is a relation field (not a scalar)
|
|
141
143
|
*/
|
|
@@ -145,7 +147,23 @@ export declare function isRelationField(fieldName: string, table: CleanTable): b
|
|
|
145
147
|
*/
|
|
146
148
|
export declare function getScalarFields(table: CleanTable): CleanField[];
|
|
147
149
|
/**
|
|
148
|
-
*
|
|
150
|
+
* Primary key field information
|
|
151
|
+
*/
|
|
152
|
+
export interface PrimaryKeyField {
|
|
153
|
+
/** Field name */
|
|
154
|
+
name: string;
|
|
155
|
+
/** GraphQL type (e.g., "UUID", "Int", "String") */
|
|
156
|
+
gqlType: string;
|
|
157
|
+
/** TypeScript type (e.g., "string", "number") */
|
|
158
|
+
tsType: string;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get primary key field information from table constraints
|
|
162
|
+
* Returns array to support composite primary keys
|
|
163
|
+
*/
|
|
164
|
+
export declare function getPrimaryKeyInfo(table: CleanTable): PrimaryKeyField[];
|
|
165
|
+
/**
|
|
166
|
+
* Get primary key field names (convenience wrapper)
|
|
149
167
|
*/
|
|
150
168
|
export declare function getPrimaryKeyFields(table: CleanTable): string[];
|
|
151
169
|
/**
|
package/cli/codegen/utils.js
CHANGED
|
@@ -32,6 +32,7 @@ exports.fieldTypeToTs = fieldTypeToTs;
|
|
|
32
32
|
exports.getScalarFilterType = getScalarFilterType;
|
|
33
33
|
exports.isRelationField = isRelationField;
|
|
34
34
|
exports.getScalarFields = getScalarFields;
|
|
35
|
+
exports.getPrimaryKeyInfo = getPrimaryKeyInfo;
|
|
35
36
|
exports.getPrimaryKeyFields = getPrimaryKeyFields;
|
|
36
37
|
exports.getQueryKeyPrefix = getQueryKeyPrefix;
|
|
37
38
|
exports.getGeneratedFileHeader = getGeneratedFileHeader;
|
|
@@ -258,10 +259,12 @@ function fieldTypeToTs(fieldType) {
|
|
|
258
259
|
// ============================================================================
|
|
259
260
|
/**
|
|
260
261
|
* Get the PostGraphile filter type for a GraphQL scalar
|
|
262
|
+
* @param gqlType - The GraphQL type string (e.g., "String", "UUID")
|
|
263
|
+
* @param isArray - Whether this is an array type
|
|
261
264
|
*/
|
|
262
|
-
function getScalarFilterType(gqlType) {
|
|
265
|
+
function getScalarFilterType(gqlType, isArray = false) {
|
|
263
266
|
const cleanType = gqlType.replace(/!/g, '');
|
|
264
|
-
return (0, scalars_1.scalarToFilterType)(cleanType);
|
|
267
|
+
return (0, scalars_1.scalarToFilterType)(cleanType, isArray);
|
|
265
268
|
}
|
|
266
269
|
// ============================================================================
|
|
267
270
|
// Field filtering utilities
|
|
@@ -283,13 +286,35 @@ function getScalarFields(table) {
|
|
|
283
286
|
return table.fields.filter((f) => !isRelationField(f.name, table));
|
|
284
287
|
}
|
|
285
288
|
/**
|
|
286
|
-
* Get primary key field
|
|
289
|
+
* Get primary key field information from table constraints
|
|
290
|
+
* Returns array to support composite primary keys
|
|
287
291
|
*/
|
|
288
|
-
function
|
|
292
|
+
function getPrimaryKeyInfo(table) {
|
|
289
293
|
const pk = table.constraints?.primaryKey?.[0];
|
|
290
|
-
if (!pk)
|
|
291
|
-
|
|
292
|
-
|
|
294
|
+
if (!pk || pk.fields.length === 0) {
|
|
295
|
+
// Fallback: try to find 'id' field in table fields
|
|
296
|
+
const idField = table.fields.find(f => f.name.toLowerCase() === 'id');
|
|
297
|
+
if (idField) {
|
|
298
|
+
return [{
|
|
299
|
+
name: idField.name,
|
|
300
|
+
gqlType: idField.type.gqlType,
|
|
301
|
+
tsType: fieldTypeToTs(idField.type),
|
|
302
|
+
}];
|
|
303
|
+
}
|
|
304
|
+
// Last resort: assume 'id' of type string (UUID)
|
|
305
|
+
return [{ name: 'id', gqlType: 'UUID', tsType: 'string' }];
|
|
306
|
+
}
|
|
307
|
+
return pk.fields.map((f) => ({
|
|
308
|
+
name: f.name,
|
|
309
|
+
gqlType: f.type.gqlType,
|
|
310
|
+
tsType: fieldTypeToTs(f.type),
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Get primary key field names (convenience wrapper)
|
|
315
|
+
*/
|
|
316
|
+
function getPrimaryKeyFields(table) {
|
|
317
|
+
return getPrimaryKeyInfo(table).map((pk) => pk.name);
|
|
293
318
|
}
|
|
294
319
|
// ============================================================================
|
|
295
320
|
// Query key generation
|
|
@@ -114,16 +114,16 @@ async function generateOrmCommand(options = {}) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
// 7. Generate ORM code
|
|
117
|
-
log('Generating
|
|
117
|
+
console.log('Generating code...');
|
|
118
118
|
const { files: generatedFiles, stats } = (0, orm_1.generateOrm)({
|
|
119
119
|
tables,
|
|
120
120
|
customOperations: customOperationsData,
|
|
121
121
|
config,
|
|
122
122
|
});
|
|
123
|
-
log(`
|
|
124
|
-
log(`
|
|
125
|
-
log(`
|
|
126
|
-
log(`
|
|
123
|
+
console.log(`Generated ${stats.totalFiles} files`);
|
|
124
|
+
log(` ${stats.tables} table models`);
|
|
125
|
+
log(` ${stats.customQueries} custom query operations`);
|
|
126
|
+
log(` ${stats.customMutations} custom mutation operations`);
|
|
127
127
|
if (options.dryRun) {
|
|
128
128
|
return {
|
|
129
129
|
success: true,
|
|
@@ -36,4 +36,7 @@ export interface WriteResult {
|
|
|
36
36
|
filesWritten?: string[];
|
|
37
37
|
errors?: string[];
|
|
38
38
|
}
|
|
39
|
-
export
|
|
39
|
+
export interface WriteOptions {
|
|
40
|
+
showProgress?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: string, subdirs: string[], options?: WriteOptions): Promise<WriteResult>;
|
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
|
|