@constructive-io/graphql-codegen 2.21.0 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/cli/codegen/barrel.d.ts +4 -1
  2. package/cli/codegen/barrel.js +18 -12
  3. package/cli/codegen/client.js +33 -0
  4. package/cli/codegen/custom-mutations.d.ts +4 -0
  5. package/cli/codegen/custom-mutations.js +39 -13
  6. package/cli/codegen/custom-queries.d.ts +4 -0
  7. package/cli/codegen/custom-queries.js +36 -11
  8. package/cli/codegen/gql-ast.js +9 -5
  9. package/cli/codegen/index.js +35 -7
  10. package/cli/codegen/mutations.d.ts +2 -0
  11. package/cli/codegen/mutations.js +87 -23
  12. package/cli/codegen/orm/barrel.js +4 -2
  13. package/cli/codegen/orm/index.js +17 -0
  14. package/cli/codegen/orm/input-types-generator.js +83 -29
  15. package/cli/codegen/orm/model-generator.js +6 -4
  16. package/cli/codegen/queries.js +36 -27
  17. package/cli/codegen/scalars.d.ts +6 -4
  18. package/cli/codegen/scalars.js +17 -9
  19. package/cli/codegen/schema-types-generator.d.ts +26 -0
  20. package/cli/codegen/schema-types-generator.js +365 -0
  21. package/cli/codegen/ts-ast.d.ts +3 -1
  22. package/cli/codegen/ts-ast.js +2 -2
  23. package/cli/codegen/type-resolver.d.ts +52 -6
  24. package/cli/codegen/type-resolver.js +97 -19
  25. package/cli/codegen/types.d.ts +7 -4
  26. package/cli/codegen/types.js +94 -41
  27. package/cli/codegen/utils.d.ts +20 -2
  28. package/cli/codegen/utils.js +32 -7
  29. package/cli/commands/generate-orm.js +5 -5
  30. package/cli/commands/generate.d.ts +4 -1
  31. package/cli/commands/generate.js +27 -8
  32. package/cli/introspect/transform-schema.d.ts +33 -21
  33. package/cli/introspect/transform-schema.js +31 -21
  34. package/esm/cli/codegen/barrel.d.ts +4 -1
  35. package/esm/cli/codegen/barrel.js +18 -12
  36. package/esm/cli/codegen/client.js +33 -0
  37. package/esm/cli/codegen/custom-mutations.d.ts +4 -0
  38. package/esm/cli/codegen/custom-mutations.js +40 -14
  39. package/esm/cli/codegen/custom-queries.d.ts +4 -0
  40. package/esm/cli/codegen/custom-queries.js +37 -12
  41. package/esm/cli/codegen/gql-ast.js +10 -6
  42. package/esm/cli/codegen/index.js +35 -7
  43. package/esm/cli/codegen/mutations.d.ts +2 -0
  44. package/esm/cli/codegen/mutations.js +88 -24
  45. package/esm/cli/codegen/orm/barrel.js +4 -2
  46. package/esm/cli/codegen/orm/index.js +17 -0
  47. package/esm/cli/codegen/orm/input-types-generator.js +83 -29
  48. package/esm/cli/codegen/orm/model-generator.js +7 -5
  49. package/esm/cli/codegen/queries.js +37 -28
  50. package/esm/cli/codegen/scalars.d.ts +6 -4
  51. package/esm/cli/codegen/scalars.js +16 -8
  52. package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
  53. package/esm/cli/codegen/schema-types-generator.js +362 -0
  54. package/esm/cli/codegen/ts-ast.d.ts +3 -1
  55. package/esm/cli/codegen/ts-ast.js +2 -2
  56. package/esm/cli/codegen/type-resolver.d.ts +52 -6
  57. package/esm/cli/codegen/type-resolver.js +97 -20
  58. package/esm/cli/codegen/types.d.ts +7 -4
  59. package/esm/cli/codegen/types.js +95 -41
  60. package/esm/cli/codegen/utils.d.ts +20 -2
  61. package/esm/cli/codegen/utils.js +31 -7
  62. package/esm/cli/commands/generate-orm.js +5 -5
  63. package/esm/cli/commands/generate.d.ts +4 -1
  64. package/esm/cli/commands/generate.js +27 -8
  65. package/esm/cli/introspect/transform-schema.d.ts +33 -21
  66. package/esm/cli/introspect/transform-schema.js +31 -21
  67. package/esm/types/schema.d.ts +2 -0
  68. package/package.json +8 -6
  69. package/types/schema.d.ts +2 -0
  70. package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  71. package/__tests__/codegen/input-types-generator.test.js +0 -635
  72. package/__tests__/codegen/react-query-optional.test.d.ts +0 -1
  73. package/__tests__/codegen/react-query-optional.test.js +0 -292
  74. package/cli/codegen/filters.d.ts +0 -27
  75. package/cli/codegen/filters.js +0 -357
  76. package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  77. package/cli/codegen/orm/input-types-generator.test.js +0 -75
  78. package/cli/codegen/orm/select-types.test.d.ts +0 -11
  79. package/cli/codegen/orm/select-types.test.js +0 -22
  80. package/cli/introspect/transform-schema.test.d.ts +0 -1
  81. package/cli/introspect/transform-schema.test.js +0 -67
  82. package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  83. package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
  84. package/esm/__tests__/codegen/react-query-optional.test.d.ts +0 -1
  85. package/esm/__tests__/codegen/react-query-optional.test.js +0 -290
  86. package/esm/cli/codegen/filters.d.ts +0 -27
  87. package/esm/cli/codegen/filters.js +0 -351
  88. package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  89. package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
  90. package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
  91. package/esm/cli/codegen/orm/select-types.test.js +0 -21
  92. package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
  93. 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
- return typeRef.name ?? 'string';
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
- return typeRef.name ?? 'unknown';
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
- * Generate types.ts content with all entity interfaces and base filter types
6
+ * Options for generating types.ts
7
7
  */
8
- export declare function generateTypesFile(tables: CleanTable[]): string;
8
+ export interface GenerateTypesOptions {
9
+ /** Enum type names that are available from schema-types.ts */
10
+ enumsFromSchemaTypes?: string[];
11
+ }
9
12
  /**
10
- * Generate a minimal entity type (just id and display fields)
13
+ * Generate types.ts content with all entity interfaces and base filter types
11
14
  */
12
- export declare function generateMinimalEntityType(table: CleanTable): string;
15
+ export declare function generateTypesFile(tables: CleanTable[], options?: GenerateTypesOptions): string;
@@ -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
- import { generateBaseFilterTypes } from './filters';
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
- // Add section comment
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 filters
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
- // Add base filter types (using string concat for complex types - acceptable for static definitions)
30
- const filterTypesContent = generateBaseFilterTypes();
31
- // Extract just the interfaces part (skip the header)
32
- const filterInterfaces = filterTypesContent
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
- * Get primary key field names
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
  /**
@@ -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 names
250
+ * Get primary key field information from table constraints
251
+ * Returns array to support composite primary keys
249
252
  */
250
- export function getPrimaryKeyFields(table) {
253
+ export function getPrimaryKeyInfo(table) {
251
254
  const pk = table.constraints?.primaryKey?.[0];
252
- if (!pk)
253
- return ['id']; // Default assumption
254
- return pk.fields.map((f) => f.name);
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 ORM client...');
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(` Generated ${stats.tables} table models`);
121
- log(` Generated ${stats.customQueries} custom query operations`);
122
- log(` Generated ${stats.customMutations} custom mutation operations`);
123
- log(` Total files: ${stats.totalFiles}`);
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 declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: string, subdirs: string[]): Promise<WriteResult>;
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(` Generated ${stats.queryHooks} table query hooks`);
122
- log(` Generated ${stats.mutationHooks} table mutation hooks`);
123
- log(` Generated ${stats.customQueryHooks} custom query hooks`);
124
- log(` Generated ${stats.customMutationHooks} custom mutation hooks`);
125
- log(` Total files: ${stats.totalFiles}`);
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 (const file of files) {
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,