@constructive-io/graphql-codegen 2.21.0 → 2.22.1
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 -7
- 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
|
@@ -6,6 +6,34 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { CleanTypeRef, CleanArgument, CleanObjectField } from '../../types/schema';
|
|
8
8
|
import type { InterfaceProperty } from './ts-ast';
|
|
9
|
+
/**
|
|
10
|
+
* Interface for tracking referenced types during code generation
|
|
11
|
+
*/
|
|
12
|
+
export interface TypeTracker {
|
|
13
|
+
/** Set of type names that have been referenced */
|
|
14
|
+
referencedTypes: Set<string>;
|
|
15
|
+
/** Track a type reference */
|
|
16
|
+
track(typeName: string): void;
|
|
17
|
+
/** Get importable types from schema-types.ts (Input/Payload/Enum types) */
|
|
18
|
+
getImportableTypes(): string[];
|
|
19
|
+
/** Get importable types from types.ts (table entity types) */
|
|
20
|
+
getTableTypes(): string[];
|
|
21
|
+
/** Reset the tracker */
|
|
22
|
+
reset(): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Options for creating a TypeTracker
|
|
26
|
+
*/
|
|
27
|
+
export interface TypeTrackerOptions {
|
|
28
|
+
/** Table entity type names that should be imported from types.ts */
|
|
29
|
+
tableTypeNames?: Set<string>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a new TypeTracker instance
|
|
33
|
+
*
|
|
34
|
+
* @param options - Optional configuration for the tracker
|
|
35
|
+
*/
|
|
36
|
+
export declare function createTypeTracker(options?: TypeTrackerOptions): TypeTracker;
|
|
9
37
|
/**
|
|
10
38
|
* Convert a GraphQL scalar type to TypeScript type
|
|
11
39
|
*/
|
|
@@ -13,13 +41,19 @@ export declare function scalarToTsType(scalarName: string): string;
|
|
|
13
41
|
/**
|
|
14
42
|
* Convert a CleanTypeRef to a TypeScript type string
|
|
15
43
|
* Handles nested LIST and NON_NULL wrappers
|
|
44
|
+
*
|
|
45
|
+
* @param typeRef - The GraphQL type reference
|
|
46
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
16
47
|
*/
|
|
17
|
-
export declare function typeRefToTsType(typeRef: CleanTypeRef): string;
|
|
48
|
+
export declare function typeRefToTsType(typeRef: CleanTypeRef, tracker?: TypeTracker): string;
|
|
18
49
|
/**
|
|
19
50
|
* Convert a CleanTypeRef to a nullable TypeScript type string
|
|
20
51
|
* (for optional fields that can be null)
|
|
52
|
+
*
|
|
53
|
+
* @param typeRef - The GraphQL type reference
|
|
54
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
21
55
|
*/
|
|
22
|
-
export declare function typeRefToNullableTsType(typeRef: CleanTypeRef): string;
|
|
56
|
+
export declare function typeRefToNullableTsType(typeRef: CleanTypeRef, tracker?: TypeTracker): string;
|
|
23
57
|
/**
|
|
24
58
|
* Check if a type reference is required (wrapped in NON_NULL)
|
|
25
59
|
*/
|
|
@@ -38,20 +72,32 @@ export declare function getTypeBaseName(typeRef: CleanTypeRef): string | null;
|
|
|
38
72
|
export declare function getBaseTypeKind(typeRef: CleanTypeRef): CleanTypeRef['kind'];
|
|
39
73
|
/**
|
|
40
74
|
* Convert CleanArgument to InterfaceProperty for ts-morph
|
|
75
|
+
*
|
|
76
|
+
* @param arg - The GraphQL argument
|
|
77
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
41
78
|
*/
|
|
42
|
-
export declare function argumentToInterfaceProperty(arg: CleanArgument): InterfaceProperty;
|
|
79
|
+
export declare function argumentToInterfaceProperty(arg: CleanArgument, tracker?: TypeTracker): InterfaceProperty;
|
|
43
80
|
/**
|
|
44
81
|
* Convert CleanObjectField to InterfaceProperty for ts-morph
|
|
82
|
+
*
|
|
83
|
+
* @param field - The GraphQL object field
|
|
84
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
45
85
|
*/
|
|
46
|
-
export declare function fieldToInterfaceProperty(field: CleanObjectField): InterfaceProperty;
|
|
86
|
+
export declare function fieldToInterfaceProperty(field: CleanObjectField, tracker?: TypeTracker): InterfaceProperty;
|
|
47
87
|
/**
|
|
48
88
|
* Convert an array of CleanArguments to InterfaceProperty array
|
|
89
|
+
*
|
|
90
|
+
* @param args - The GraphQL arguments
|
|
91
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
49
92
|
*/
|
|
50
|
-
export declare function argumentsToInterfaceProperties(args: CleanArgument[]): InterfaceProperty[];
|
|
93
|
+
export declare function argumentsToInterfaceProperties(args: CleanArgument[], tracker?: TypeTracker): InterfaceProperty[];
|
|
51
94
|
/**
|
|
52
95
|
* Convert an array of CleanObjectFields to InterfaceProperty array
|
|
96
|
+
*
|
|
97
|
+
* @param fields - The GraphQL object fields
|
|
98
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
53
99
|
*/
|
|
54
|
-
export declare function fieldsToInterfaceProperties(fields: CleanObjectField[]): InterfaceProperty[];
|
|
100
|
+
export declare function fieldsToInterfaceProperties(fields: CleanObjectField[], tracker?: TypeTracker): InterfaceProperty[];
|
|
55
101
|
/**
|
|
56
102
|
* Check if a field should be skipped in selections
|
|
57
103
|
*/
|
|
@@ -1,4 +1,57 @@
|
|
|
1
|
-
import { scalarToTsType as resolveScalarToTs } from './scalars';
|
|
1
|
+
import { scalarToTsType as resolveScalarToTs, SCALAR_NAMES } from './scalars';
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Type Tracker for Collecting Referenced Types
|
|
4
|
+
// ============================================================================
|
|
5
|
+
/**
|
|
6
|
+
* Types that should not be tracked (scalars, built-ins, internal types)
|
|
7
|
+
*/
|
|
8
|
+
const SKIP_TYPE_TRACKING = new Set([
|
|
9
|
+
...SCALAR_NAMES,
|
|
10
|
+
// GraphQL built-ins
|
|
11
|
+
'Query',
|
|
12
|
+
'Mutation',
|
|
13
|
+
'Subscription',
|
|
14
|
+
'__Schema',
|
|
15
|
+
'__Type',
|
|
16
|
+
'__Field',
|
|
17
|
+
'__InputValue',
|
|
18
|
+
'__EnumValue',
|
|
19
|
+
'__Directive',
|
|
20
|
+
// Connection types (handled separately)
|
|
21
|
+
'PageInfo',
|
|
22
|
+
]);
|
|
23
|
+
/**
|
|
24
|
+
* Create a new TypeTracker instance
|
|
25
|
+
*
|
|
26
|
+
* @param options - Optional configuration for the tracker
|
|
27
|
+
*/
|
|
28
|
+
export function createTypeTracker(options) {
|
|
29
|
+
const referencedTypes = new Set();
|
|
30
|
+
const tableTypeNames = options?.tableTypeNames ?? new Set();
|
|
31
|
+
return {
|
|
32
|
+
referencedTypes,
|
|
33
|
+
track(typeName) {
|
|
34
|
+
if (typeName && !SKIP_TYPE_TRACKING.has(typeName)) {
|
|
35
|
+
referencedTypes.add(typeName);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
getImportableTypes() {
|
|
39
|
+
// Return schema types (not table entity types)
|
|
40
|
+
return Array.from(referencedTypes)
|
|
41
|
+
.filter((name) => !tableTypeNames.has(name))
|
|
42
|
+
.sort();
|
|
43
|
+
},
|
|
44
|
+
getTableTypes() {
|
|
45
|
+
// Return table entity types only
|
|
46
|
+
return Array.from(referencedTypes)
|
|
47
|
+
.filter((name) => tableTypeNames.has(name))
|
|
48
|
+
.sort();
|
|
49
|
+
},
|
|
50
|
+
reset() {
|
|
51
|
+
referencedTypes.clear();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
2
55
|
// ============================================================================
|
|
3
56
|
// GraphQL to TypeScript Type Mapping
|
|
4
57
|
// ============================================================================
|
|
@@ -14,32 +67,41 @@ export function scalarToTsType(scalarName) {
|
|
|
14
67
|
/**
|
|
15
68
|
* Convert a CleanTypeRef to a TypeScript type string
|
|
16
69
|
* Handles nested LIST and NON_NULL wrappers
|
|
70
|
+
*
|
|
71
|
+
* @param typeRef - The GraphQL type reference
|
|
72
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
17
73
|
*/
|
|
18
|
-
export function typeRefToTsType(typeRef) {
|
|
74
|
+
export function typeRefToTsType(typeRef, tracker) {
|
|
19
75
|
switch (typeRef.kind) {
|
|
20
76
|
case 'NON_NULL':
|
|
21
77
|
// Non-null wrapper - unwrap and return the inner type
|
|
22
78
|
if (typeRef.ofType) {
|
|
23
|
-
return typeRefToTsType(typeRef.ofType);
|
|
79
|
+
return typeRefToTsType(typeRef.ofType, tracker);
|
|
24
80
|
}
|
|
25
81
|
return 'unknown';
|
|
26
82
|
case 'LIST':
|
|
27
83
|
// List wrapper - wrap inner type in array
|
|
28
84
|
if (typeRef.ofType) {
|
|
29
|
-
const innerType = typeRefToTsType(typeRef.ofType);
|
|
85
|
+
const innerType = typeRefToTsType(typeRef.ofType, tracker);
|
|
30
86
|
return `${innerType}[]`;
|
|
31
87
|
}
|
|
32
88
|
return 'unknown[]';
|
|
33
89
|
case 'SCALAR':
|
|
34
90
|
// Scalar type - map to TS type
|
|
35
91
|
return scalarToTsType(typeRef.name ?? 'unknown');
|
|
36
|
-
case 'ENUM':
|
|
37
|
-
// Enum type - use the GraphQL enum name
|
|
38
|
-
|
|
92
|
+
case 'ENUM': {
|
|
93
|
+
// Enum type - use the GraphQL enum name and track it
|
|
94
|
+
const typeName = typeRef.name ?? 'string';
|
|
95
|
+
tracker?.track(typeName);
|
|
96
|
+
return typeName;
|
|
97
|
+
}
|
|
39
98
|
case 'OBJECT':
|
|
40
|
-
case 'INPUT_OBJECT':
|
|
41
|
-
// Object types - use the GraphQL type name
|
|
42
|
-
|
|
99
|
+
case 'INPUT_OBJECT': {
|
|
100
|
+
// Object types - use the GraphQL type name and track it
|
|
101
|
+
const typeName = typeRef.name ?? 'unknown';
|
|
102
|
+
tracker?.track(typeName);
|
|
103
|
+
return typeName;
|
|
104
|
+
}
|
|
43
105
|
default:
|
|
44
106
|
return 'unknown';
|
|
45
107
|
}
|
|
@@ -47,9 +109,12 @@ export function typeRefToTsType(typeRef) {
|
|
|
47
109
|
/**
|
|
48
110
|
* Convert a CleanTypeRef to a nullable TypeScript type string
|
|
49
111
|
* (for optional fields that can be null)
|
|
112
|
+
*
|
|
113
|
+
* @param typeRef - The GraphQL type reference
|
|
114
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
50
115
|
*/
|
|
51
|
-
export function typeRefToNullableTsType(typeRef) {
|
|
52
|
-
const baseType = typeRefToTsType(typeRef);
|
|
116
|
+
export function typeRefToNullableTsType(typeRef, tracker) {
|
|
117
|
+
const baseType = typeRefToTsType(typeRef, tracker);
|
|
53
118
|
// If the outer type is NON_NULL, it's required
|
|
54
119
|
if (typeRef.kind === 'NON_NULL') {
|
|
55
120
|
return baseType;
|
|
@@ -100,37 +165,49 @@ export function getBaseTypeKind(typeRef) {
|
|
|
100
165
|
// ============================================================================
|
|
101
166
|
/**
|
|
102
167
|
* Convert CleanArgument to InterfaceProperty for ts-morph
|
|
168
|
+
*
|
|
169
|
+
* @param arg - The GraphQL argument
|
|
170
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
103
171
|
*/
|
|
104
|
-
export function argumentToInterfaceProperty(arg) {
|
|
172
|
+
export function argumentToInterfaceProperty(arg, tracker) {
|
|
105
173
|
return {
|
|
106
174
|
name: arg.name,
|
|
107
|
-
type: typeRefToTsType(arg.type),
|
|
175
|
+
type: typeRefToTsType(arg.type, tracker),
|
|
108
176
|
optional: !isTypeRequired(arg.type),
|
|
109
177
|
docs: arg.description ? [arg.description] : undefined,
|
|
110
178
|
};
|
|
111
179
|
}
|
|
112
180
|
/**
|
|
113
181
|
* Convert CleanObjectField to InterfaceProperty for ts-morph
|
|
182
|
+
*
|
|
183
|
+
* @param field - The GraphQL object field
|
|
184
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
114
185
|
*/
|
|
115
|
-
export function fieldToInterfaceProperty(field) {
|
|
186
|
+
export function fieldToInterfaceProperty(field, tracker) {
|
|
116
187
|
return {
|
|
117
188
|
name: field.name,
|
|
118
|
-
type: typeRefToNullableTsType(field.type),
|
|
189
|
+
type: typeRefToNullableTsType(field.type, tracker),
|
|
119
190
|
optional: false, // Fields are always present, just potentially null
|
|
120
191
|
docs: field.description ? [field.description] : undefined,
|
|
121
192
|
};
|
|
122
193
|
}
|
|
123
194
|
/**
|
|
124
195
|
* Convert an array of CleanArguments to InterfaceProperty array
|
|
196
|
+
*
|
|
197
|
+
* @param args - The GraphQL arguments
|
|
198
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
125
199
|
*/
|
|
126
|
-
export function argumentsToInterfaceProperties(args) {
|
|
127
|
-
return args.map(argumentToInterfaceProperty);
|
|
200
|
+
export function argumentsToInterfaceProperties(args, tracker) {
|
|
201
|
+
return args.map((arg) => argumentToInterfaceProperty(arg, tracker));
|
|
128
202
|
}
|
|
129
203
|
/**
|
|
130
204
|
* Convert an array of CleanObjectFields to InterfaceProperty array
|
|
205
|
+
*
|
|
206
|
+
* @param fields - The GraphQL object fields
|
|
207
|
+
* @param tracker - Optional TypeTracker to collect referenced types
|
|
131
208
|
*/
|
|
132
|
-
export function fieldsToInterfaceProperties(fields) {
|
|
133
|
-
return fields.map(fieldToInterfaceProperty);
|
|
209
|
+
export function fieldsToInterfaceProperties(fields, tracker) {
|
|
210
|
+
return fields.map((field) => fieldToInterfaceProperty(field, tracker));
|
|
134
211
|
}
|
|
135
212
|
// ============================================================================
|
|
136
213
|
// Type Filtering
|
|
@@ -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/esm/cli/codegen/types.js
CHANGED
|
@@ -1,15 +1,100 @@
|
|
|
1
|
-
import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createInterface, } from './ts-ast';
|
|
1
|
+
import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, } from './ts-ast';
|
|
2
2
|
import { getScalarFields, fieldTypeToTs } from './utils';
|
|
3
|
-
|
|
3
|
+
/** All filter type configurations - scalar and list filters */
|
|
4
|
+
const FILTER_CONFIGS = [
|
|
5
|
+
// Scalar filters
|
|
6
|
+
{ name: 'StringFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'string'] },
|
|
7
|
+
{ name: 'IntFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
8
|
+
{ name: 'FloatFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
9
|
+
{ name: 'BooleanFilter', tsType: 'boolean', operators: ['equality'] },
|
|
10
|
+
{ name: 'UUIDFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray'] },
|
|
11
|
+
{ name: 'DatetimeFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
12
|
+
{ name: 'DateFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
13
|
+
{ name: 'JSONFilter', tsType: 'Record<string, unknown>', operators: ['equality', 'distinct', 'json'] },
|
|
14
|
+
{ name: 'BigIntFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
15
|
+
{ name: 'BigFloatFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
16
|
+
{ name: 'BitStringFilter', tsType: 'string', operators: ['equality'] },
|
|
17
|
+
{ name: 'InternetAddressFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'] },
|
|
18
|
+
{ name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
|
|
19
|
+
// List filters
|
|
20
|
+
{ name: 'StringListFilter', tsType: 'string[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
|
|
21
|
+
{ name: 'IntListFilter', tsType: 'number[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
|
|
22
|
+
{ name: 'UUIDListFilter', tsType: 'string[]', operators: ['equality', 'distinct', 'comparison', 'listArray'] },
|
|
23
|
+
];
|
|
24
|
+
/** Build filter properties based on operator sets */
|
|
25
|
+
function buildFilterProperties(tsType, operators) {
|
|
26
|
+
const props = [];
|
|
27
|
+
// Equality operators (isNull, equalTo, notEqualTo)
|
|
28
|
+
if (operators.includes('equality')) {
|
|
29
|
+
props.push({ name: 'isNull', type: 'boolean', optional: true }, { name: 'equalTo', type: tsType, optional: true }, { name: 'notEqualTo', type: tsType, optional: true });
|
|
30
|
+
}
|
|
31
|
+
// Distinct operators
|
|
32
|
+
if (operators.includes('distinct')) {
|
|
33
|
+
props.push({ name: 'distinctFrom', type: tsType, optional: true }, { name: 'notDistinctFrom', type: tsType, optional: true });
|
|
34
|
+
}
|
|
35
|
+
// In array operators
|
|
36
|
+
if (operators.includes('inArray')) {
|
|
37
|
+
props.push({ name: 'in', type: `${tsType}[]`, optional: true }, { name: 'notIn', type: `${tsType}[]`, optional: true });
|
|
38
|
+
}
|
|
39
|
+
// Comparison operators (lt, lte, gt, gte)
|
|
40
|
+
if (operators.includes('comparison')) {
|
|
41
|
+
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 });
|
|
42
|
+
}
|
|
43
|
+
// String-specific operators
|
|
44
|
+
if (operators.includes('string')) {
|
|
45
|
+
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 });
|
|
46
|
+
}
|
|
47
|
+
// JSON-specific operators
|
|
48
|
+
if (operators.includes('json')) {
|
|
49
|
+
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 });
|
|
50
|
+
}
|
|
51
|
+
// Internet address operators
|
|
52
|
+
if (operators.includes('inet')) {
|
|
53
|
+
props.push({ name: 'contains', type: 'string', optional: true }, { name: 'containedBy', type: 'string', optional: true }, { name: 'containsOrContainedBy', type: 'string', optional: true });
|
|
54
|
+
}
|
|
55
|
+
// Full-text search operator
|
|
56
|
+
if (operators.includes('fulltext')) {
|
|
57
|
+
props.push({ name: 'matches', type: 'string', optional: true });
|
|
58
|
+
}
|
|
59
|
+
// List/Array operators (for StringListFilter, IntListFilter, etc.)
|
|
60
|
+
if (operators.includes('listArray')) {
|
|
61
|
+
// Extract base type from array type (e.g., 'string[]' -> 'string')
|
|
62
|
+
const baseType = tsType.replace('[]', '');
|
|
63
|
+
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 });
|
|
64
|
+
}
|
|
65
|
+
return props;
|
|
66
|
+
}
|
|
4
67
|
/**
|
|
5
68
|
* Generate types.ts content with all entity interfaces and base filter types
|
|
6
69
|
*/
|
|
7
|
-
export function generateTypesFile(tables) {
|
|
70
|
+
export function generateTypesFile(tables, options = {}) {
|
|
71
|
+
const { enumsFromSchemaTypes = [] } = options;
|
|
72
|
+
const enumSet = new Set(enumsFromSchemaTypes);
|
|
8
73
|
const project = createProject();
|
|
9
74
|
const sourceFile = createSourceFile(project, 'types.ts');
|
|
10
75
|
// Add file header
|
|
11
76
|
sourceFile.insertText(0, createFileHeader('Entity types and filter types') + '\n\n');
|
|
12
|
-
//
|
|
77
|
+
// Collect which enums are actually used by entity fields
|
|
78
|
+
const usedEnums = new Set();
|
|
79
|
+
for (const table of tables) {
|
|
80
|
+
const scalarFields = getScalarFields(table);
|
|
81
|
+
for (const field of scalarFields) {
|
|
82
|
+
// Check if the field's gqlType matches any known enum
|
|
83
|
+
const cleanType = field.type.gqlType.replace(/!/g, '');
|
|
84
|
+
if (enumSet.has(cleanType)) {
|
|
85
|
+
usedEnums.add(cleanType);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Add import for enum types from schema-types if any are used
|
|
90
|
+
if (usedEnums.size > 0) {
|
|
91
|
+
sourceFile.addImportDeclaration(createImport({
|
|
92
|
+
moduleSpecifier: './schema-types',
|
|
93
|
+
typeOnlyNamedImports: Array.from(usedEnums).sort(),
|
|
94
|
+
}));
|
|
95
|
+
sourceFile.addStatements('');
|
|
96
|
+
}
|
|
97
|
+
// Add section comment for entity types
|
|
13
98
|
sourceFile.addStatements('// ============================================================================');
|
|
14
99
|
sourceFile.addStatements('// Entity types');
|
|
15
100
|
sourceFile.addStatements('// ============================================================================\n');
|
|
@@ -22,44 +107,13 @@ export function generateTypesFile(tables) {
|
|
|
22
107
|
}));
|
|
23
108
|
sourceFile.addInterface(createInterface(table.name, properties));
|
|
24
109
|
}
|
|
25
|
-
// Add section comment for
|
|
110
|
+
// Add section comment for filter types
|
|
26
111
|
sourceFile.addStatements('\n// ============================================================================');
|
|
27
|
-
sourceFile.addStatements('// Filter types (shared)');
|
|
112
|
+
sourceFile.addStatements('// Filter types (shared PostGraphile filter interfaces)');
|
|
28
113
|
sourceFile.addStatements('// ============================================================================\n');
|
|
29
|
-
//
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.split('\n')
|
|
34
|
-
.slice(6) // Skip header lines
|
|
35
|
-
.join('\n');
|
|
36
|
-
sourceFile.addStatements(filterInterfaces);
|
|
37
|
-
return getFormattedOutput(sourceFile);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Generate a minimal entity type (just id and display fields)
|
|
41
|
-
*/
|
|
42
|
-
export function generateMinimalEntityType(table) {
|
|
43
|
-
const project = createProject();
|
|
44
|
-
const sourceFile = createSourceFile(project, 'minimal.ts');
|
|
45
|
-
const scalarFields = getScalarFields(table);
|
|
46
|
-
// Find id and likely display fields
|
|
47
|
-
const displayFields = scalarFields.filter((f) => {
|
|
48
|
-
const name = f.name.toLowerCase();
|
|
49
|
-
return (name === 'id' ||
|
|
50
|
-
name === 'name' ||
|
|
51
|
-
name === 'title' ||
|
|
52
|
-
name === 'label' ||
|
|
53
|
-
name === 'email' ||
|
|
54
|
-
name.endsWith('name') ||
|
|
55
|
-
name.endsWith('title'));
|
|
56
|
-
});
|
|
57
|
-
// If no display fields found, take first 5 scalar fields
|
|
58
|
-
const fieldsToUse = displayFields.length > 0 ? displayFields : scalarFields.slice(0, 5);
|
|
59
|
-
const properties = fieldsToUse.map((field) => ({
|
|
60
|
-
name: field.name,
|
|
61
|
-
type: `${fieldTypeToTs(field.type)} | null`,
|
|
62
|
-
}));
|
|
63
|
-
sourceFile.addInterface(createInterface(`${table.name}Minimal`, properties));
|
|
114
|
+
// Generate all filter types
|
|
115
|
+
for (const { name, tsType, operators } of FILTER_CONFIGS) {
|
|
116
|
+
sourceFile.addInterface(createInterface(name, buildFilterProperties(tsType, operators)));
|
|
117
|
+
}
|
|
64
118
|
return getFormattedOutput(sourceFile);
|
|
65
119
|
}
|
|
@@ -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/esm/cli/codegen/utils.js
CHANGED
|
@@ -220,10 +220,12 @@ export function fieldTypeToTs(fieldType) {
|
|
|
220
220
|
// ============================================================================
|
|
221
221
|
/**
|
|
222
222
|
* Get the PostGraphile filter type for a GraphQL scalar
|
|
223
|
+
* @param gqlType - The GraphQL type string (e.g., "String", "UUID")
|
|
224
|
+
* @param isArray - Whether this is an array type
|
|
223
225
|
*/
|
|
224
|
-
export function getScalarFilterType(gqlType) {
|
|
226
|
+
export function getScalarFilterType(gqlType, isArray = false) {
|
|
225
227
|
const cleanType = gqlType.replace(/!/g, '');
|
|
226
|
-
return scalarToFilterType(cleanType);
|
|
228
|
+
return scalarToFilterType(cleanType, isArray);
|
|
227
229
|
}
|
|
228
230
|
// ============================================================================
|
|
229
231
|
// Field filtering utilities
|
|
@@ -245,13 +247,35 @@ export function getScalarFields(table) {
|
|
|
245
247
|
return table.fields.filter((f) => !isRelationField(f.name, table));
|
|
246
248
|
}
|
|
247
249
|
/**
|
|
248
|
-
* Get primary key field
|
|
250
|
+
* Get primary key field information from table constraints
|
|
251
|
+
* Returns array to support composite primary keys
|
|
249
252
|
*/
|
|
250
|
-
export function
|
|
253
|
+
export function getPrimaryKeyInfo(table) {
|
|
251
254
|
const pk = table.constraints?.primaryKey?.[0];
|
|
252
|
-
if (!pk)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
if (!pk || pk.fields.length === 0) {
|
|
256
|
+
// Fallback: try to find 'id' field in table fields
|
|
257
|
+
const idField = table.fields.find(f => f.name.toLowerCase() === 'id');
|
|
258
|
+
if (idField) {
|
|
259
|
+
return [{
|
|
260
|
+
name: idField.name,
|
|
261
|
+
gqlType: idField.type.gqlType,
|
|
262
|
+
tsType: fieldTypeToTs(idField.type),
|
|
263
|
+
}];
|
|
264
|
+
}
|
|
265
|
+
// Last resort: assume 'id' of type string (UUID)
|
|
266
|
+
return [{ name: 'id', gqlType: 'UUID', tsType: 'string' }];
|
|
267
|
+
}
|
|
268
|
+
return pk.fields.map((f) => ({
|
|
269
|
+
name: f.name,
|
|
270
|
+
gqlType: f.type.gqlType,
|
|
271
|
+
tsType: fieldTypeToTs(f.type),
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get primary key field names (convenience wrapper)
|
|
276
|
+
*/
|
|
277
|
+
export function getPrimaryKeyFields(table) {
|
|
278
|
+
return getPrimaryKeyInfo(table).map((pk) => pk.name);
|
|
255
279
|
}
|
|
256
280
|
// ============================================================================
|
|
257
281
|
// Query key generation
|
|
@@ -111,16 +111,16 @@ export async function generateOrmCommand(options = {}) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
// 7. Generate ORM code
|
|
114
|
-
log('Generating
|
|
114
|
+
console.log('Generating code...');
|
|
115
115
|
const { files: generatedFiles, stats } = generateOrm({
|
|
116
116
|
tables,
|
|
117
117
|
customOperations: customOperationsData,
|
|
118
118
|
config,
|
|
119
119
|
});
|
|
120
|
-
log(`
|
|
121
|
-
log(`
|
|
122
|
-
log(`
|
|
123
|
-
log(`
|
|
120
|
+
console.log(`Generated ${stats.totalFiles} files`);
|
|
121
|
+
log(` ${stats.tables} table models`);
|
|
122
|
+
log(` ${stats.customQueries} custom query operations`);
|
|
123
|
+
log(` ${stats.customMutations} custom mutation operations`);
|
|
124
124
|
if (options.dryRun) {
|
|
125
125
|
return {
|
|
126
126
|
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>;
|
|
@@ -112,17 +112,17 @@ export async function generateCommand(options = {}) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
// 7. Generate code
|
|
115
|
-
log('Generating code...');
|
|
115
|
+
console.log('Generating code...');
|
|
116
116
|
const { files: generatedFiles, stats } = generate({
|
|
117
117
|
tables,
|
|
118
118
|
customOperations: customOperationsData,
|
|
119
119
|
config,
|
|
120
120
|
});
|
|
121
|
-
log(`
|
|
122
|
-
log(`
|
|
123
|
-
log(`
|
|
124
|
-
log(`
|
|
125
|
-
log(`
|
|
121
|
+
console.log(`Generated ${stats.totalFiles} files`);
|
|
122
|
+
log(` ${stats.queryHooks} table query hooks`);
|
|
123
|
+
log(` ${stats.mutationHooks} table mutation hooks`);
|
|
124
|
+
log(` ${stats.customQueryHooks} custom query hooks`);
|
|
125
|
+
log(` ${stats.customMutationHooks} custom mutation hooks`);
|
|
126
126
|
if (options.dryRun) {
|
|
127
127
|
return {
|
|
128
128
|
success: true,
|
|
@@ -191,9 +191,12 @@ async function loadConfig(options) {
|
|
|
191
191
|
const config = resolveConfig(mergedConfig);
|
|
192
192
|
return { success: true, config };
|
|
193
193
|
}
|
|
194
|
-
export async function writeGeneratedFiles(files, outputDir, subdirs) {
|
|
194
|
+
export async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
|
|
195
|
+
const { showProgress = true } = options;
|
|
195
196
|
const errors = [];
|
|
196
197
|
const written = [];
|
|
198
|
+
const total = files.length;
|
|
199
|
+
const isTTY = process.stdout.isTTY;
|
|
197
200
|
// Ensure output directory exists
|
|
198
201
|
try {
|
|
199
202
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -219,8 +222,20 @@ export async function writeGeneratedFiles(files, outputDir, subdirs) {
|
|
|
219
222
|
if (errors.length > 0) {
|
|
220
223
|
return { success: false, errors };
|
|
221
224
|
}
|
|
222
|
-
for (
|
|
225
|
+
for (let i = 0; i < files.length; i++) {
|
|
226
|
+
const file = files[i];
|
|
223
227
|
const filePath = path.join(outputDir, file.path);
|
|
228
|
+
// Show progress
|
|
229
|
+
if (showProgress) {
|
|
230
|
+
const progress = Math.round(((i + 1) / total) * 100);
|
|
231
|
+
if (isTTY) {
|
|
232
|
+
process.stdout.write(`\rWriting files: ${i + 1}/${total} (${progress}%)`);
|
|
233
|
+
}
|
|
234
|
+
else if (i % 100 === 0 || i === total - 1) {
|
|
235
|
+
// Non-TTY: periodic updates for CI/CD
|
|
236
|
+
console.log(`Writing files: ${i + 1}/${total}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
224
239
|
// Ensure parent directory exists
|
|
225
240
|
const parentDir = path.dirname(filePath);
|
|
226
241
|
try {
|
|
@@ -240,6 +255,10 @@ export async function writeGeneratedFiles(files, outputDir, subdirs) {
|
|
|
240
255
|
errors.push(`Failed to write ${filePath}: ${message}`);
|
|
241
256
|
}
|
|
242
257
|
}
|
|
258
|
+
// Clear progress line
|
|
259
|
+
if (showProgress && isTTY) {
|
|
260
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
261
|
+
}
|
|
243
262
|
return {
|
|
244
263
|
success: errors.length === 0,
|
|
245
264
|
filesWritten: written,
|