@constructive-io/graphql-codegen 2.19.0 → 2.20.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 +1818 -113
- package/__tests__/codegen/input-types-generator.test.d.ts +1 -0
- package/__tests__/codegen/input-types-generator.test.js +635 -0
- package/cli/codegen/barrel.d.ts +27 -0
- package/cli/codegen/barrel.js +163 -0
- package/cli/codegen/client.d.ts +4 -0
- package/cli/codegen/client.js +170 -0
- package/cli/codegen/custom-mutations.d.ts +38 -0
- package/cli/codegen/custom-mutations.js +149 -0
- package/cli/codegen/custom-queries.d.ts +38 -0
- package/cli/codegen/custom-queries.js +358 -0
- package/cli/codegen/filters.d.ts +27 -0
- package/cli/codegen/filters.js +357 -0
- package/cli/codegen/gql-ast.d.ts +41 -0
- package/cli/codegen/gql-ast.js +329 -0
- package/cli/codegen/index.d.ts +71 -0
- package/cli/codegen/index.js +147 -0
- package/cli/codegen/mutations.d.ts +30 -0
- package/cli/codegen/mutations.js +410 -0
- package/cli/codegen/orm/barrel.d.ts +18 -0
- package/cli/codegen/orm/barrel.js +48 -0
- package/cli/codegen/orm/client-generator.d.ts +45 -0
- package/cli/codegen/orm/client-generator.js +646 -0
- package/cli/codegen/orm/custom-ops-generator.d.ts +30 -0
- package/cli/codegen/orm/custom-ops-generator.js +350 -0
- package/cli/codegen/orm/index.d.ts +38 -0
- package/cli/codegen/orm/index.js +88 -0
- package/cli/codegen/orm/input-types-generator.d.ts +21 -0
- package/cli/codegen/orm/input-types-generator.js +705 -0
- package/cli/codegen/orm/input-types-generator.test.d.ts +1 -0
- package/cli/codegen/orm/input-types-generator.test.js +75 -0
- package/cli/codegen/orm/model-generator.d.ts +32 -0
- package/cli/codegen/orm/model-generator.js +264 -0
- package/cli/codegen/orm/query-builder.d.ts +161 -0
- package/cli/codegen/orm/query-builder.js +366 -0
- package/cli/codegen/orm/select-types.d.ts +169 -0
- package/cli/codegen/orm/select-types.js +16 -0
- package/cli/codegen/orm/select-types.test.d.ts +11 -0
- package/cli/codegen/orm/select-types.test.js +22 -0
- package/cli/codegen/queries.d.ts +25 -0
- package/cli/codegen/queries.js +438 -0
- package/cli/codegen/scalars.d.ts +12 -0
- package/cli/codegen/scalars.js +71 -0
- package/cli/codegen/schema-gql-ast.d.ts +51 -0
- package/cli/codegen/schema-gql-ast.js +385 -0
- package/cli/codegen/ts-ast.d.ts +122 -0
- package/cli/codegen/ts-ast.js +280 -0
- package/cli/codegen/type-resolver.d.ts +96 -0
- package/cli/codegen/type-resolver.js +246 -0
- package/cli/codegen/types.d.ts +12 -0
- package/cli/codegen/types.js +69 -0
- package/cli/codegen/utils.d.ts +163 -0
- package/cli/codegen/utils.js +326 -0
- package/cli/commands/generate-orm.d.ts +37 -0
- package/cli/commands/generate-orm.js +195 -0
- package/cli/commands/generate.d.ts +39 -0
- package/cli/commands/generate.js +299 -0
- package/cli/commands/index.d.ts +7 -0
- package/cli/commands/index.js +12 -0
- package/cli/commands/init.d.ts +35 -0
- package/cli/commands/init.js +176 -0
- package/cli/index.d.ts +4 -0
- package/cli/index.js +291 -0
- package/cli/introspect/fetch-meta.d.ts +31 -0
- package/cli/introspect/fetch-meta.js +108 -0
- package/cli/introspect/fetch-schema.d.ts +21 -0
- package/cli/introspect/fetch-schema.js +86 -0
- package/cli/introspect/index.d.ts +8 -0
- package/cli/introspect/index.js +16 -0
- package/cli/introspect/meta-query.d.ts +111 -0
- package/cli/introspect/meta-query.js +191 -0
- package/cli/introspect/schema-query.d.ts +20 -0
- package/cli/introspect/schema-query.js +123 -0
- package/cli/introspect/transform-schema.d.ts +74 -0
- package/cli/introspect/transform-schema.js +269 -0
- package/cli/introspect/transform-schema.test.d.ts +1 -0
- package/cli/introspect/transform-schema.test.js +67 -0
- package/cli/introspect/transform.d.ts +21 -0
- package/cli/introspect/transform.js +216 -0
- package/cli/watch/cache.d.ts +45 -0
- package/cli/watch/cache.js +111 -0
- package/cli/watch/debounce.d.ts +19 -0
- package/cli/watch/debounce.js +89 -0
- package/cli/watch/hash.d.ts +17 -0
- package/cli/watch/hash.js +48 -0
- package/cli/watch/index.d.ts +10 -0
- package/cli/watch/index.js +22 -0
- package/cli/watch/orchestrator.d.ts +63 -0
- package/cli/watch/orchestrator.js +228 -0
- package/cli/watch/poller.d.ts +65 -0
- package/cli/watch/poller.js +203 -0
- package/cli/watch/types.d.ts +67 -0
- package/cli/watch/types.js +5 -0
- package/client/error.d.ts +95 -0
- package/client/error.js +255 -0
- package/client/execute.d.ts +57 -0
- package/client/execute.js +124 -0
- package/client/index.d.ts +6 -0
- package/client/index.js +18 -0
- package/client/typed-document.d.ts +31 -0
- package/client/typed-document.js +44 -0
- package/core/ast.d.ts +10 -0
- package/core/ast.js +593 -0
- package/core/custom-ast.d.ts +35 -0
- package/core/custom-ast.js +204 -0
- package/core/index.d.ts +8 -0
- package/core/index.js +33 -0
- package/core/meta-object/convert.d.ts +65 -0
- package/core/meta-object/convert.js +63 -0
- package/core/meta-object/format.json +93 -0
- package/core/meta-object/index.d.ts +2 -0
- package/core/meta-object/index.js +18 -0
- package/core/meta-object/validate.d.ts +9 -0
- package/core/meta-object/validate.js +34 -0
- package/core/query-builder.d.ts +46 -0
- package/core/query-builder.js +412 -0
- package/core/types.d.ts +139 -0
- package/core/types.js +28 -0
- package/esm/__tests__/codegen/input-types-generator.test.d.ts +1 -0
- package/esm/__tests__/codegen/input-types-generator.test.js +633 -0
- package/esm/cli/codegen/barrel.d.ts +27 -0
- package/esm/cli/codegen/barrel.js +156 -0
- package/esm/cli/codegen/client.d.ts +4 -0
- package/esm/cli/codegen/client.js +167 -0
- package/esm/cli/codegen/custom-mutations.d.ts +38 -0
- package/esm/cli/codegen/custom-mutations.js +145 -0
- package/esm/cli/codegen/custom-queries.d.ts +38 -0
- package/esm/cli/codegen/custom-queries.js +354 -0
- package/esm/cli/codegen/filters.d.ts +27 -0
- package/esm/cli/codegen/filters.js +351 -0
- package/esm/cli/codegen/gql-ast.d.ts +41 -0
- package/esm/cli/codegen/gql-ast.js +288 -0
- package/esm/cli/codegen/index.d.ts +71 -0
- package/esm/cli/codegen/index.js +124 -0
- package/esm/cli/codegen/mutations.d.ts +30 -0
- package/esm/cli/codegen/mutations.js +404 -0
- package/esm/cli/codegen/orm/barrel.d.ts +18 -0
- package/esm/cli/codegen/orm/barrel.js +44 -0
- package/esm/cli/codegen/orm/client-generator.d.ts +45 -0
- package/esm/cli/codegen/orm/client-generator.js +640 -0
- package/esm/cli/codegen/orm/custom-ops-generator.d.ts +30 -0
- package/esm/cli/codegen/orm/custom-ops-generator.js +346 -0
- package/esm/cli/codegen/orm/index.d.ts +38 -0
- package/esm/cli/codegen/orm/index.js +75 -0
- package/esm/cli/codegen/orm/input-types-generator.d.ts +21 -0
- package/esm/cli/codegen/orm/input-types-generator.js +700 -0
- package/esm/cli/codegen/orm/input-types-generator.test.d.ts +1 -0
- package/esm/cli/codegen/orm/input-types-generator.test.js +73 -0
- package/esm/cli/codegen/orm/model-generator.d.ts +32 -0
- package/esm/cli/codegen/orm/model-generator.js +260 -0
- package/esm/cli/codegen/orm/query-builder.d.ts +161 -0
- package/esm/cli/codegen/orm/query-builder.js +353 -0
- package/esm/cli/codegen/orm/select-types.d.ts +169 -0
- package/esm/cli/codegen/orm/select-types.js +15 -0
- package/esm/cli/codegen/orm/select-types.test.d.ts +11 -0
- package/esm/cli/codegen/orm/select-types.test.js +21 -0
- package/esm/cli/codegen/queries.d.ts +25 -0
- package/esm/cli/codegen/queries.js +433 -0
- package/esm/cli/codegen/scalars.d.ts +12 -0
- package/esm/cli/codegen/scalars.js +66 -0
- package/esm/cli/codegen/schema-gql-ast.d.ts +51 -0
- package/esm/cli/codegen/schema-gql-ast.js +343 -0
- package/esm/cli/codegen/ts-ast.d.ts +122 -0
- package/esm/cli/codegen/ts-ast.js +260 -0
- package/esm/cli/codegen/type-resolver.d.ts +96 -0
- package/esm/cli/codegen/type-resolver.js +224 -0
- package/esm/cli/codegen/types.d.ts +12 -0
- package/esm/cli/codegen/types.js +65 -0
- package/esm/cli/codegen/utils.d.ts +163 -0
- package/esm/cli/codegen/utils.js +288 -0
- package/esm/cli/commands/generate-orm.d.ts +37 -0
- package/esm/cli/commands/generate-orm.js +192 -0
- package/esm/cli/commands/generate.d.ts +39 -0
- package/esm/cli/commands/generate.js +262 -0
- package/esm/cli/commands/index.d.ts +7 -0
- package/esm/cli/commands/index.js +5 -0
- package/esm/cli/commands/init.d.ts +35 -0
- package/esm/cli/commands/init.js +138 -0
- package/esm/cli/index.d.ts +4 -0
- package/esm/cli/index.js +256 -0
- package/esm/cli/introspect/fetch-meta.d.ts +31 -0
- package/esm/cli/introspect/fetch-meta.js +104 -0
- package/esm/cli/introspect/fetch-schema.d.ts +21 -0
- package/esm/cli/introspect/fetch-schema.js +83 -0
- package/esm/cli/introspect/index.d.ts +8 -0
- package/esm/cli/introspect/index.js +6 -0
- package/esm/cli/introspect/meta-query.d.ts +111 -0
- package/esm/cli/introspect/meta-query.js +188 -0
- package/esm/cli/introspect/schema-query.d.ts +20 -0
- package/esm/cli/introspect/schema-query.js +120 -0
- package/esm/cli/introspect/transform-schema.d.ts +74 -0
- package/esm/cli/introspect/transform-schema.js +259 -0
- package/esm/cli/introspect/transform-schema.test.d.ts +1 -0
- package/esm/cli/introspect/transform-schema.test.js +65 -0
- package/esm/cli/introspect/transform.d.ts +21 -0
- package/esm/cli/introspect/transform.js +210 -0
- package/esm/cli/watch/cache.d.ts +45 -0
- package/esm/cli/watch/cache.js +73 -0
- package/esm/cli/watch/debounce.d.ts +19 -0
- package/esm/cli/watch/debounce.js +85 -0
- package/esm/cli/watch/hash.d.ts +17 -0
- package/esm/cli/watch/hash.js +43 -0
- package/esm/cli/watch/index.d.ts +10 -0
- package/esm/cli/watch/index.js +8 -0
- package/esm/cli/watch/orchestrator.d.ts +63 -0
- package/esm/cli/watch/orchestrator.js +223 -0
- package/esm/cli/watch/poller.d.ts +65 -0
- package/esm/cli/watch/poller.js +198 -0
- package/esm/cli/watch/types.d.ts +67 -0
- package/esm/cli/watch/types.js +4 -0
- package/esm/client/error.d.ts +95 -0
- package/esm/client/error.js +249 -0
- package/esm/client/execute.d.ts +57 -0
- package/esm/client/execute.js +120 -0
- package/esm/client/index.d.ts +6 -0
- package/esm/client/index.js +6 -0
- package/esm/client/typed-document.d.ts +31 -0
- package/esm/client/typed-document.js +40 -0
- package/esm/core/ast.d.ts +10 -0
- package/esm/core/ast.js +549 -0
- package/esm/core/custom-ast.d.ts +35 -0
- package/esm/core/custom-ast.js +161 -0
- package/esm/core/index.d.ts +8 -0
- package/esm/core/index.js +12 -0
- package/esm/core/meta-object/convert.d.ts +65 -0
- package/esm/core/meta-object/convert.js +60 -0
- package/esm/core/meta-object/format.json +93 -0
- package/esm/core/meta-object/index.d.ts +2 -0
- package/esm/core/meta-object/index.js +2 -0
- package/esm/core/meta-object/validate.d.ts +9 -0
- package/esm/core/meta-object/validate.js +28 -0
- package/esm/core/query-builder.d.ts +46 -0
- package/esm/core/query-builder.js +375 -0
- package/esm/core/types.d.ts +139 -0
- package/esm/core/types.js +24 -0
- package/esm/generators/field-selector.d.ts +30 -0
- package/esm/generators/field-selector.js +355 -0
- package/esm/generators/index.d.ts +6 -0
- package/esm/generators/index.js +9 -0
- package/esm/generators/mutations.d.ts +31 -0
- package/esm/generators/mutations.js +197 -0
- package/esm/generators/select.d.ts +50 -0
- package/esm/generators/select.js +636 -0
- package/esm/index.d.ts +12 -0
- package/esm/index.js +17 -3
- package/esm/react/index.d.ts +5 -0
- package/esm/react/index.js +6 -0
- package/esm/types/config.d.ts +199 -0
- package/esm/types/config.js +106 -0
- package/esm/types/index.d.ts +9 -0
- package/esm/types/index.js +4 -0
- package/esm/types/introspection.d.ts +121 -0
- package/esm/types/introspection.js +54 -0
- package/esm/types/mutation.d.ts +45 -0
- package/esm/types/mutation.js +4 -0
- package/esm/types/query.d.ts +82 -0
- package/esm/types/query.js +4 -0
- package/esm/types/schema.d.ts +253 -0
- package/esm/types/schema.js +5 -0
- package/esm/types/selection.d.ts +43 -0
- package/esm/types/selection.js +4 -0
- package/esm/utils/index.d.ts +4 -0
- package/esm/utils/index.js +4 -0
- package/generators/field-selector.d.ts +30 -0
- package/generators/field-selector.js +361 -0
- package/generators/index.d.ts +6 -0
- package/generators/index.js +27 -0
- package/generators/mutations.d.ts +31 -0
- package/generators/mutations.js +235 -0
- package/generators/select.d.ts +50 -0
- package/generators/select.js +679 -0
- package/index.d.ts +12 -3
- package/index.js +19 -3
- package/package.json +59 -38
- package/react/index.d.ts +5 -0
- package/react/index.js +9 -0
- package/types/config.d.ts +199 -0
- package/types/config.js +111 -0
- package/types/index.d.ts +9 -0
- package/types/index.js +10 -0
- package/types/introspection.d.ts +121 -0
- package/types/introspection.js +62 -0
- package/types/mutation.d.ts +45 -0
- package/types/mutation.js +5 -0
- package/types/query.d.ts +82 -0
- package/types/query.js +5 -0
- package/types/schema.d.ts +253 -0
- package/types/schema.js +6 -0
- package/types/selection.d.ts +43 -0
- package/types/selection.js +5 -0
- package/utils/index.d.ts +4 -0
- package/utils/index.js +7 -0
- package/codegen.d.ts +0 -13
- package/codegen.js +0 -293
- package/esm/codegen.js +0 -253
- package/esm/gql.js +0 -939
- package/esm/options.js +0 -27
- package/gql.d.ts +0 -188
- package/gql.js +0 -992
- package/options.d.ts +0 -45
- package/options.js +0 -31
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import { createProject, createSourceFile, getMinimalFormattedOutput, createFileHeader, createInterface, createTypeAlias, addSectionComment, } from '../ts-ast';
|
|
2
|
+
import { getTableNames, getFilterTypeName, getOrderByTypeName, isRelationField, } from '../utils';
|
|
3
|
+
import { getTypeBaseName } from '../type-resolver';
|
|
4
|
+
import { scalarToTsType, scalarToFilterType } from '../scalars';
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Constants
|
|
7
|
+
// ============================================================================
|
|
8
|
+
/** Fields excluded from Create/Update inputs (auto-generated or system fields) */
|
|
9
|
+
const EXCLUDED_MUTATION_FIELDS = ['id', 'createdAt', 'updatedAt', 'nodeId'];
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Type Conversion Utilities
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Overrides for input-type generation
|
|
15
|
+
*/
|
|
16
|
+
const INPUT_SCALAR_OVERRIDES = {
|
|
17
|
+
JSON: 'Record<string, unknown>',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Convert GraphQL scalar to TypeScript type
|
|
21
|
+
*/
|
|
22
|
+
function scalarToInputTs(scalar) {
|
|
23
|
+
return scalarToTsType(scalar, {
|
|
24
|
+
unknownScalar: 'name',
|
|
25
|
+
overrides: INPUT_SCALAR_OVERRIDES,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert a CleanTypeRef to TypeScript type string
|
|
30
|
+
*/
|
|
31
|
+
function typeRefToTs(typeRef) {
|
|
32
|
+
if (typeRef.kind === 'NON_NULL') {
|
|
33
|
+
if (typeRef.ofType) {
|
|
34
|
+
return typeRefToTs(typeRef.ofType);
|
|
35
|
+
}
|
|
36
|
+
return typeRef.name ?? 'unknown';
|
|
37
|
+
}
|
|
38
|
+
if (typeRef.kind === 'LIST') {
|
|
39
|
+
if (typeRef.ofType) {
|
|
40
|
+
return `${typeRefToTs(typeRef.ofType)}[]`;
|
|
41
|
+
}
|
|
42
|
+
return 'unknown[]';
|
|
43
|
+
}
|
|
44
|
+
// Scalar or named type
|
|
45
|
+
const name = typeRef.name ?? 'unknown';
|
|
46
|
+
return scalarToInputTs(name);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if a type is required (NON_NULL)
|
|
50
|
+
*/
|
|
51
|
+
function isRequired(typeRef) {
|
|
52
|
+
return typeRef.kind === 'NON_NULL';
|
|
53
|
+
}
|
|
54
|
+
/** Configuration for all scalar filter types - matches PostGraphile's generated filters */
|
|
55
|
+
const SCALAR_FILTER_CONFIGS = [
|
|
56
|
+
{ name: 'StringFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'string'] },
|
|
57
|
+
{ name: 'IntFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
58
|
+
{ name: 'FloatFilter', tsType: 'number', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
59
|
+
{ name: 'BooleanFilter', tsType: 'boolean', operators: ['equality'] },
|
|
60
|
+
{ name: 'UUIDFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray'] },
|
|
61
|
+
{ name: 'DatetimeFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
62
|
+
{ name: 'DateFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
63
|
+
{ name: 'JSONFilter', tsType: 'Record<string, unknown>', operators: ['equality', 'distinct', 'json'] },
|
|
64
|
+
{ name: 'BigIntFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
65
|
+
{ name: 'BigFloatFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison'] },
|
|
66
|
+
{ name: 'BitStringFilter', tsType: 'string', operators: ['equality'] },
|
|
67
|
+
{ name: 'InternetAddressFilter', tsType: 'string', operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'] },
|
|
68
|
+
{ name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* Build filter properties based on operator sets
|
|
72
|
+
*/
|
|
73
|
+
function buildScalarFilterProperties(config) {
|
|
74
|
+
const { tsType, operators } = config;
|
|
75
|
+
const props = [];
|
|
76
|
+
// Equality operators (isNull, equalTo, notEqualTo)
|
|
77
|
+
if (operators.includes('equality')) {
|
|
78
|
+
props.push({ name: 'isNull', type: 'boolean', optional: true }, { name: 'equalTo', type: tsType, optional: true }, { name: 'notEqualTo', type: tsType, optional: true });
|
|
79
|
+
}
|
|
80
|
+
// Distinct operators
|
|
81
|
+
if (operators.includes('distinct')) {
|
|
82
|
+
props.push({ name: 'distinctFrom', type: tsType, optional: true }, { name: 'notDistinctFrom', type: tsType, optional: true });
|
|
83
|
+
}
|
|
84
|
+
// In/notIn operators
|
|
85
|
+
if (operators.includes('inArray')) {
|
|
86
|
+
props.push({ name: 'in', type: `${tsType}[]`, optional: true }, { name: 'notIn', type: `${tsType}[]`, optional: true });
|
|
87
|
+
}
|
|
88
|
+
// Comparison operators (less than, greater than)
|
|
89
|
+
if (operators.includes('comparison')) {
|
|
90
|
+
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 });
|
|
91
|
+
}
|
|
92
|
+
// String operators (includes, startsWith, like, etc.)
|
|
93
|
+
if (operators.includes('string')) {
|
|
94
|
+
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 });
|
|
95
|
+
}
|
|
96
|
+
// JSON operators (contains, containsKey, etc.)
|
|
97
|
+
if (operators.includes('json')) {
|
|
98
|
+
props.push({ name: 'contains', type: 'Record<string, unknown>', optional: true }, { name: 'containedBy', type: 'Record<string, unknown>', optional: true }, { name: 'containsKey', type: 'string', optional: true }, { name: 'containsAllKeys', type: 'string[]', optional: true }, { name: 'containsAnyKeys', type: 'string[]', optional: true });
|
|
99
|
+
}
|
|
100
|
+
// Internet address operators
|
|
101
|
+
if (operators.includes('inet')) {
|
|
102
|
+
props.push({ name: 'contains', type: 'string', optional: true }, { name: 'containsOrEqualTo', type: 'string', optional: true }, { name: 'containedBy', type: 'string', optional: true }, { name: 'containedByOrEqualTo', type: 'string', optional: true }, { name: 'containsOrContainedBy', type: 'string', optional: true });
|
|
103
|
+
}
|
|
104
|
+
// Full-text search operators
|
|
105
|
+
if (operators.includes('fulltext')) {
|
|
106
|
+
props.push({ name: 'matches', type: 'string', optional: true });
|
|
107
|
+
}
|
|
108
|
+
return props;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Add scalar filter types to source file using ts-morph
|
|
112
|
+
*/
|
|
113
|
+
function addScalarFilterTypes(sourceFile) {
|
|
114
|
+
addSectionComment(sourceFile, 'Scalar Filter Types');
|
|
115
|
+
for (const config of SCALAR_FILTER_CONFIGS) {
|
|
116
|
+
sourceFile.addInterface(createInterface(config.name, buildScalarFilterProperties(config)));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// Entity Types Generator (AST-based)
|
|
121
|
+
// ============================================================================
|
|
122
|
+
/**
|
|
123
|
+
* Build properties for an entity interface
|
|
124
|
+
*/
|
|
125
|
+
function buildEntityProperties(table) {
|
|
126
|
+
const properties = [];
|
|
127
|
+
for (const field of table.fields) {
|
|
128
|
+
if (isRelationField(field.name, table))
|
|
129
|
+
continue;
|
|
130
|
+
const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
|
|
131
|
+
const tsType = scalarToInputTs(fieldType);
|
|
132
|
+
const isNullable = field.name !== 'id' && field.name !== 'nodeId';
|
|
133
|
+
properties.push({
|
|
134
|
+
name: field.name,
|
|
135
|
+
type: isNullable ? `${tsType} | null` : tsType,
|
|
136
|
+
optional: isNullable,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return properties;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Add entity type interface for a table
|
|
143
|
+
*/
|
|
144
|
+
function addEntityType(sourceFile, table) {
|
|
145
|
+
const { typeName } = getTableNames(table);
|
|
146
|
+
sourceFile.addInterface(createInterface(typeName, buildEntityProperties(table)));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Add all entity types
|
|
150
|
+
*/
|
|
151
|
+
function addEntityTypes(sourceFile, tables) {
|
|
152
|
+
addSectionComment(sourceFile, 'Entity Types');
|
|
153
|
+
for (const table of tables) {
|
|
154
|
+
addEntityType(sourceFile, table);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// Relation Helper Types Generator (AST-based)
|
|
159
|
+
// ============================================================================
|
|
160
|
+
/**
|
|
161
|
+
* Add relation helper types (ConnectionResult, PageInfo)
|
|
162
|
+
*/
|
|
163
|
+
function addRelationHelperTypes(sourceFile) {
|
|
164
|
+
addSectionComment(sourceFile, 'Relation Helper Types');
|
|
165
|
+
sourceFile.addInterface(createInterface('ConnectionResult<T>', [
|
|
166
|
+
{ name: 'nodes', type: 'T[]', optional: false },
|
|
167
|
+
{ name: 'totalCount', type: 'number', optional: false },
|
|
168
|
+
{ name: 'pageInfo', type: 'PageInfo', optional: false },
|
|
169
|
+
]));
|
|
170
|
+
sourceFile.addInterface(createInterface('PageInfo', [
|
|
171
|
+
{ name: 'hasNextPage', type: 'boolean', optional: false },
|
|
172
|
+
{ name: 'hasPreviousPage', type: 'boolean', optional: false },
|
|
173
|
+
{ name: 'startCursor', type: 'string | null', optional: true },
|
|
174
|
+
{ name: 'endCursor', type: 'string | null', optional: true },
|
|
175
|
+
]));
|
|
176
|
+
}
|
|
177
|
+
// ============================================================================
|
|
178
|
+
// Entity Relation Types Generator (AST-based)
|
|
179
|
+
// ============================================================================
|
|
180
|
+
function getRelatedTypeName(tableName, tableByName) {
|
|
181
|
+
const relatedTable = tableByName.get(tableName);
|
|
182
|
+
return relatedTable ? getTableNames(relatedTable).typeName : tableName;
|
|
183
|
+
}
|
|
184
|
+
function getRelatedOrderByName(tableName, tableByName) {
|
|
185
|
+
const relatedTable = tableByName.get(tableName);
|
|
186
|
+
return relatedTable ? getOrderByTypeName(relatedTable) : `${tableName}sOrderBy`;
|
|
187
|
+
}
|
|
188
|
+
function getRelatedFilterName(tableName, tableByName) {
|
|
189
|
+
const relatedTable = tableByName.get(tableName);
|
|
190
|
+
return relatedTable ? getFilterTypeName(relatedTable) : `${tableName}Filter`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Build properties for entity relations interface
|
|
194
|
+
*/
|
|
195
|
+
function buildEntityRelationProperties(table, tableByName) {
|
|
196
|
+
const properties = [];
|
|
197
|
+
for (const relation of table.relations.belongsTo) {
|
|
198
|
+
if (!relation.fieldName)
|
|
199
|
+
continue;
|
|
200
|
+
const relatedTypeName = getRelatedTypeName(relation.referencesTable, tableByName);
|
|
201
|
+
properties.push({
|
|
202
|
+
name: relation.fieldName,
|
|
203
|
+
type: `${relatedTypeName} | null`,
|
|
204
|
+
optional: true,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
for (const relation of table.relations.hasOne) {
|
|
208
|
+
if (!relation.fieldName)
|
|
209
|
+
continue;
|
|
210
|
+
const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
|
|
211
|
+
properties.push({
|
|
212
|
+
name: relation.fieldName,
|
|
213
|
+
type: `${relatedTypeName} | null`,
|
|
214
|
+
optional: true,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
for (const relation of table.relations.hasMany) {
|
|
218
|
+
if (!relation.fieldName)
|
|
219
|
+
continue;
|
|
220
|
+
const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
|
|
221
|
+
properties.push({
|
|
222
|
+
name: relation.fieldName,
|
|
223
|
+
type: `ConnectionResult<${relatedTypeName}>`,
|
|
224
|
+
optional: true,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
for (const relation of table.relations.manyToMany) {
|
|
228
|
+
if (!relation.fieldName)
|
|
229
|
+
continue;
|
|
230
|
+
const relatedTypeName = getRelatedTypeName(relation.rightTable, tableByName);
|
|
231
|
+
properties.push({
|
|
232
|
+
name: relation.fieldName,
|
|
233
|
+
type: `ConnectionResult<${relatedTypeName}>`,
|
|
234
|
+
optional: true,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return properties;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Add entity relation types
|
|
241
|
+
*/
|
|
242
|
+
function addEntityRelationTypes(sourceFile, tables, tableByName) {
|
|
243
|
+
addSectionComment(sourceFile, 'Entity Relation Types');
|
|
244
|
+
for (const table of tables) {
|
|
245
|
+
const { typeName } = getTableNames(table);
|
|
246
|
+
sourceFile.addInterface(createInterface(`${typeName}Relations`, buildEntityRelationProperties(table, tableByName)));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Add entity types with relations (intersection types)
|
|
251
|
+
*/
|
|
252
|
+
function addEntityWithRelations(sourceFile, tables) {
|
|
253
|
+
addSectionComment(sourceFile, 'Entity Types With Relations');
|
|
254
|
+
for (const table of tables) {
|
|
255
|
+
const { typeName } = getTableNames(table);
|
|
256
|
+
sourceFile.addTypeAlias(createTypeAlias(`${typeName}WithRelations`, `${typeName} & ${typeName}Relations`));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// Entity Select Types Generator (AST-based)
|
|
261
|
+
// ============================================================================
|
|
262
|
+
/**
|
|
263
|
+
* Build the type string for a Select type (as object type literal)
|
|
264
|
+
*/
|
|
265
|
+
function buildSelectTypeBody(table, tableByName) {
|
|
266
|
+
const lines = ['{'];
|
|
267
|
+
// Add scalar fields
|
|
268
|
+
for (const field of table.fields) {
|
|
269
|
+
if (!isRelationField(field.name, table)) {
|
|
270
|
+
lines.push(`${field.name}?: boolean;`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Add belongsTo relations
|
|
274
|
+
for (const relation of table.relations.belongsTo) {
|
|
275
|
+
if (relation.fieldName) {
|
|
276
|
+
const relatedTypeName = getRelatedTypeName(relation.referencesTable, tableByName);
|
|
277
|
+
lines.push(`${relation.fieldName}?: boolean | { select?: ${relatedTypeName}Select };`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Add hasMany relations
|
|
281
|
+
for (const relation of table.relations.hasMany) {
|
|
282
|
+
if (relation.fieldName) {
|
|
283
|
+
const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
|
|
284
|
+
const filterName = getRelatedFilterName(relation.referencedByTable, tableByName);
|
|
285
|
+
const orderByName = getRelatedOrderByName(relation.referencedByTable, tableByName);
|
|
286
|
+
lines.push(`${relation.fieldName}?: boolean | {`);
|
|
287
|
+
lines.push(` select?: ${relatedTypeName}Select;`);
|
|
288
|
+
lines.push(` first?: number;`);
|
|
289
|
+
lines.push(` filter?: ${filterName};`);
|
|
290
|
+
lines.push(` orderBy?: ${orderByName}[];`);
|
|
291
|
+
lines.push(`};`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Add manyToMany relations
|
|
295
|
+
for (const relation of table.relations.manyToMany) {
|
|
296
|
+
if (relation.fieldName) {
|
|
297
|
+
const relatedTypeName = getRelatedTypeName(relation.rightTable, tableByName);
|
|
298
|
+
const filterName = getRelatedFilterName(relation.rightTable, tableByName);
|
|
299
|
+
const orderByName = getRelatedOrderByName(relation.rightTable, tableByName);
|
|
300
|
+
lines.push(`${relation.fieldName}?: boolean | {`);
|
|
301
|
+
lines.push(` select?: ${relatedTypeName}Select;`);
|
|
302
|
+
lines.push(` first?: number;`);
|
|
303
|
+
lines.push(` filter?: ${filterName};`);
|
|
304
|
+
lines.push(` orderBy?: ${orderByName}[];`);
|
|
305
|
+
lines.push(`};`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Add hasOne relations
|
|
309
|
+
for (const relation of table.relations.hasOne) {
|
|
310
|
+
if (relation.fieldName) {
|
|
311
|
+
const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
|
|
312
|
+
lines.push(`${relation.fieldName}?: boolean | { select?: ${relatedTypeName}Select };`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
lines.push('}');
|
|
316
|
+
return lines.join('\n');
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Add entity Select types
|
|
320
|
+
*/
|
|
321
|
+
function addEntitySelectTypes(sourceFile, tables, tableByName) {
|
|
322
|
+
addSectionComment(sourceFile, 'Entity Select Types');
|
|
323
|
+
for (const table of tables) {
|
|
324
|
+
const { typeName } = getTableNames(table);
|
|
325
|
+
sourceFile.addTypeAlias(createTypeAlias(`${typeName}Select`, buildSelectTypeBody(table, tableByName)));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// Table Filter Types Generator (AST-based)
|
|
330
|
+
// ============================================================================
|
|
331
|
+
/**
|
|
332
|
+
* Map field type to filter type
|
|
333
|
+
*/
|
|
334
|
+
function getFilterTypeForField(fieldType) {
|
|
335
|
+
return scalarToFilterType(fieldType) ?? 'StringFilter';
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Build properties for a table filter interface
|
|
339
|
+
*/
|
|
340
|
+
function buildTableFilterProperties(table) {
|
|
341
|
+
const filterName = getFilterTypeName(table);
|
|
342
|
+
const properties = [];
|
|
343
|
+
for (const field of table.fields) {
|
|
344
|
+
const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
|
|
345
|
+
if (isRelationField(field.name, table))
|
|
346
|
+
continue;
|
|
347
|
+
const filterType = getFilterTypeForField(fieldType);
|
|
348
|
+
properties.push({ name: field.name, type: filterType, optional: true });
|
|
349
|
+
}
|
|
350
|
+
// Add logical operators
|
|
351
|
+
properties.push({ name: 'and', type: `${filterName}[]`, optional: true });
|
|
352
|
+
properties.push({ name: 'or', type: `${filterName}[]`, optional: true });
|
|
353
|
+
properties.push({ name: 'not', type: filterName, optional: true });
|
|
354
|
+
return properties;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Add table filter types
|
|
358
|
+
*/
|
|
359
|
+
function addTableFilterTypes(sourceFile, tables) {
|
|
360
|
+
addSectionComment(sourceFile, 'Table Filter Types');
|
|
361
|
+
for (const table of tables) {
|
|
362
|
+
const filterName = getFilterTypeName(table);
|
|
363
|
+
sourceFile.addInterface(createInterface(filterName, buildTableFilterProperties(table)));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// ============================================================================
|
|
367
|
+
// OrderBy Types Generator (AST-based)
|
|
368
|
+
// ============================================================================
|
|
369
|
+
/**
|
|
370
|
+
* Build OrderBy union type string
|
|
371
|
+
*/
|
|
372
|
+
function buildOrderByUnion(table) {
|
|
373
|
+
const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
|
|
374
|
+
for (const field of table.fields) {
|
|
375
|
+
if (isRelationField(field.name, table))
|
|
376
|
+
continue;
|
|
377
|
+
const upperSnake = field.name.replace(/([A-Z])/g, '_$1').toUpperCase();
|
|
378
|
+
values.push(`${upperSnake}_ASC`);
|
|
379
|
+
values.push(`${upperSnake}_DESC`);
|
|
380
|
+
}
|
|
381
|
+
return values.map((v) => `'${v}'`).join(' | ');
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Add OrderBy types
|
|
385
|
+
*/
|
|
386
|
+
function addOrderByTypes(sourceFile, tables) {
|
|
387
|
+
addSectionComment(sourceFile, 'OrderBy Types');
|
|
388
|
+
for (const table of tables) {
|
|
389
|
+
const enumName = getOrderByTypeName(table);
|
|
390
|
+
sourceFile.addTypeAlias(createTypeAlias(enumName, buildOrderByUnion(table)));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// ============================================================================
|
|
394
|
+
// CRUD Input Types Generator (AST-based)
|
|
395
|
+
// ============================================================================
|
|
396
|
+
/**
|
|
397
|
+
* Build the nested data object fields for Create input
|
|
398
|
+
*/
|
|
399
|
+
function buildCreateDataFields(table) {
|
|
400
|
+
const fields = [];
|
|
401
|
+
for (const field of table.fields) {
|
|
402
|
+
if (EXCLUDED_MUTATION_FIELDS.includes(field.name))
|
|
403
|
+
continue;
|
|
404
|
+
if (isRelationField(field.name, table))
|
|
405
|
+
continue;
|
|
406
|
+
const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
|
|
407
|
+
const tsType = scalarToInputTs(fieldType);
|
|
408
|
+
const isOptional = !field.name.endsWith('Id');
|
|
409
|
+
fields.push({ name: field.name, type: tsType, optional: isOptional });
|
|
410
|
+
}
|
|
411
|
+
return fields;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Generate Create input interface as formatted string.
|
|
415
|
+
*
|
|
416
|
+
* ts-morph doesn't handle nested object types in interface properties well,
|
|
417
|
+
* so we build this manually with pre-doubled indentation (4→2, 8→4) since
|
|
418
|
+
* getMinimalFormattedOutput halves all indentation.
|
|
419
|
+
*/
|
|
420
|
+
function buildCreateInputInterface(table) {
|
|
421
|
+
const { typeName, singularName } = getTableNames(table);
|
|
422
|
+
const fields = buildCreateDataFields(table);
|
|
423
|
+
const lines = [
|
|
424
|
+
`export interface Create${typeName}Input {`,
|
|
425
|
+
` clientMutationId?: string;`,
|
|
426
|
+
` ${singularName}: {`,
|
|
427
|
+
];
|
|
428
|
+
for (const field of fields) {
|
|
429
|
+
const opt = field.optional ? '?' : '';
|
|
430
|
+
lines.push(` ${field.name}${opt}: ${field.type};`);
|
|
431
|
+
}
|
|
432
|
+
lines.push(' };');
|
|
433
|
+
lines.push('}');
|
|
434
|
+
return lines.join('\n');
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Build Patch type properties
|
|
438
|
+
*/
|
|
439
|
+
function buildPatchProperties(table) {
|
|
440
|
+
const properties = [];
|
|
441
|
+
for (const field of table.fields) {
|
|
442
|
+
if (EXCLUDED_MUTATION_FIELDS.includes(field.name))
|
|
443
|
+
continue;
|
|
444
|
+
if (isRelationField(field.name, table))
|
|
445
|
+
continue;
|
|
446
|
+
const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
|
|
447
|
+
const tsType = scalarToInputTs(fieldType);
|
|
448
|
+
properties.push({ name: field.name, type: `${tsType} | null`, optional: true });
|
|
449
|
+
}
|
|
450
|
+
return properties;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Add CRUD input types for a table
|
|
454
|
+
*/
|
|
455
|
+
function addCrudInputTypes(sourceFile, table) {
|
|
456
|
+
const { typeName } = getTableNames(table);
|
|
457
|
+
const patchName = `${typeName}Patch`;
|
|
458
|
+
// Create input - build as raw statement due to nested object type formatting
|
|
459
|
+
sourceFile.addStatements(buildCreateInputInterface(table));
|
|
460
|
+
// Patch interface
|
|
461
|
+
sourceFile.addInterface(createInterface(patchName, buildPatchProperties(table)));
|
|
462
|
+
// Update input
|
|
463
|
+
sourceFile.addInterface(createInterface(`Update${typeName}Input`, [
|
|
464
|
+
{ name: 'clientMutationId', type: 'string', optional: true },
|
|
465
|
+
{ name: 'id', type: 'string', optional: false },
|
|
466
|
+
{ name: 'patch', type: patchName, optional: false },
|
|
467
|
+
]));
|
|
468
|
+
// Delete input
|
|
469
|
+
sourceFile.addInterface(createInterface(`Delete${typeName}Input`, [
|
|
470
|
+
{ name: 'clientMutationId', type: 'string', optional: true },
|
|
471
|
+
{ name: 'id', type: 'string', optional: false },
|
|
472
|
+
]));
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Add all CRUD input types
|
|
476
|
+
*/
|
|
477
|
+
function addAllCrudInputTypes(sourceFile, tables) {
|
|
478
|
+
addSectionComment(sourceFile, 'CRUD Input Types');
|
|
479
|
+
for (const table of tables) {
|
|
480
|
+
addCrudInputTypes(sourceFile, table);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// ============================================================================
|
|
484
|
+
// Custom Input Types Generator (AST-based)
|
|
485
|
+
// ============================================================================
|
|
486
|
+
/**
|
|
487
|
+
* Collect all input type names used by operations
|
|
488
|
+
*/
|
|
489
|
+
export function collectInputTypeNames(operations) {
|
|
490
|
+
const inputTypes = new Set();
|
|
491
|
+
function collectFromTypeRef(typeRef) {
|
|
492
|
+
const baseName = getTypeBaseName(typeRef);
|
|
493
|
+
if (baseName && baseName.endsWith('Input')) {
|
|
494
|
+
inputTypes.add(baseName);
|
|
495
|
+
}
|
|
496
|
+
if (baseName && baseName.endsWith('Filter')) {
|
|
497
|
+
inputTypes.add(baseName);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
for (const op of operations) {
|
|
501
|
+
for (const arg of op.args) {
|
|
502
|
+
collectFromTypeRef(arg.type);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return inputTypes;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Add custom input types from TypeRegistry
|
|
509
|
+
*/
|
|
510
|
+
function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes) {
|
|
511
|
+
addSectionComment(sourceFile, 'Custom Input Types (from schema)');
|
|
512
|
+
const generatedTypes = new Set();
|
|
513
|
+
const typesToGenerate = new Set(Array.from(usedInputTypes));
|
|
514
|
+
// Filter out types we've already generated
|
|
515
|
+
const typesToRemove = [];
|
|
516
|
+
typesToGenerate.forEach((typeName) => {
|
|
517
|
+
if (typeName.endsWith('Filter') ||
|
|
518
|
+
typeName.startsWith('Create') ||
|
|
519
|
+
typeName.startsWith('Update') ||
|
|
520
|
+
typeName.startsWith('Delete')) {
|
|
521
|
+
const isTableCrud = /^(Create|Update|Delete)[A-Z][a-zA-Z]+Input$/.test(typeName) ||
|
|
522
|
+
/^[A-Z][a-zA-Z]+Filter$/.test(typeName);
|
|
523
|
+
if (isTableCrud) {
|
|
524
|
+
typesToRemove.push(typeName);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
typesToRemove.forEach((t) => typesToGenerate.delete(t));
|
|
529
|
+
let iterations = 0;
|
|
530
|
+
const maxIterations = 200;
|
|
531
|
+
while (typesToGenerate.size > 0 && iterations < maxIterations) {
|
|
532
|
+
iterations++;
|
|
533
|
+
const typeNameResult = typesToGenerate.values().next();
|
|
534
|
+
if (typeNameResult.done)
|
|
535
|
+
break;
|
|
536
|
+
const typeName = typeNameResult.value;
|
|
537
|
+
typesToGenerate.delete(typeName);
|
|
538
|
+
if (generatedTypes.has(typeName))
|
|
539
|
+
continue;
|
|
540
|
+
generatedTypes.add(typeName);
|
|
541
|
+
const typeInfo = typeRegistry.get(typeName);
|
|
542
|
+
if (!typeInfo) {
|
|
543
|
+
sourceFile.addStatements(`// Type '${typeName}' not found in schema`);
|
|
544
|
+
sourceFile.addTypeAlias(createTypeAlias(typeName, 'Record<string, unknown>'));
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (typeInfo.kind === 'INPUT_OBJECT' && typeInfo.inputFields) {
|
|
548
|
+
const properties = [];
|
|
549
|
+
for (const field of typeInfo.inputFields) {
|
|
550
|
+
const optional = !isRequired(field.type);
|
|
551
|
+
const tsType = typeRefToTs(field.type);
|
|
552
|
+
properties.push({ name: field.name, type: tsType, optional });
|
|
553
|
+
// Follow nested Input types
|
|
554
|
+
const baseType = getTypeBaseName(field.type);
|
|
555
|
+
if (baseType && baseType.endsWith('Input') && !generatedTypes.has(baseType)) {
|
|
556
|
+
typesToGenerate.add(baseType);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
sourceFile.addInterface(createInterface(typeName, properties));
|
|
560
|
+
}
|
|
561
|
+
else if (typeInfo.kind === 'ENUM' && typeInfo.enumValues) {
|
|
562
|
+
const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
|
|
563
|
+
sourceFile.addTypeAlias(createTypeAlias(typeName, values));
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
sourceFile.addStatements(`// Type '${typeName}' is ${typeInfo.kind}`);
|
|
567
|
+
sourceFile.addTypeAlias(createTypeAlias(typeName, 'unknown'));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// ============================================================================
|
|
572
|
+
// Payload/Return Types Generator (AST-based)
|
|
573
|
+
// ============================================================================
|
|
574
|
+
/**
|
|
575
|
+
* Collect all payload type names from operation return types
|
|
576
|
+
*/
|
|
577
|
+
export function collectPayloadTypeNames(operations) {
|
|
578
|
+
const payloadTypes = new Set();
|
|
579
|
+
for (const op of operations) {
|
|
580
|
+
const baseName = getTypeBaseName(op.returnType);
|
|
581
|
+
if (baseName && (baseName.endsWith('Payload') || !baseName.endsWith('Connection'))) {
|
|
582
|
+
payloadTypes.add(baseName);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return payloadTypes;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Add payload/return types
|
|
589
|
+
*/
|
|
590
|
+
function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGeneratedTypes) {
|
|
591
|
+
addSectionComment(sourceFile, 'Payload/Return Types (for custom operations)');
|
|
592
|
+
const generatedTypes = new Set(alreadyGeneratedTypes);
|
|
593
|
+
const typesToGenerate = new Set(Array.from(usedPayloadTypes));
|
|
594
|
+
const skipTypes = new Set([
|
|
595
|
+
'String', 'Int', 'Float', 'Boolean', 'ID', 'UUID', 'Datetime', 'Date',
|
|
596
|
+
'Time', 'JSON', 'BigInt', 'BigFloat', 'Cursor', 'Query', 'Mutation',
|
|
597
|
+
]);
|
|
598
|
+
let iterations = 0;
|
|
599
|
+
const maxIterations = 200;
|
|
600
|
+
while (typesToGenerate.size > 0 && iterations < maxIterations) {
|
|
601
|
+
iterations++;
|
|
602
|
+
const typeNameResult = typesToGenerate.values().next();
|
|
603
|
+
if (typeNameResult.done)
|
|
604
|
+
break;
|
|
605
|
+
const typeName = typeNameResult.value;
|
|
606
|
+
typesToGenerate.delete(typeName);
|
|
607
|
+
if (generatedTypes.has(typeName) || skipTypes.has(typeName))
|
|
608
|
+
continue;
|
|
609
|
+
const typeInfo = typeRegistry.get(typeName);
|
|
610
|
+
if (!typeInfo)
|
|
611
|
+
continue;
|
|
612
|
+
if (typeInfo.kind !== 'OBJECT' || !typeInfo.fields)
|
|
613
|
+
continue;
|
|
614
|
+
generatedTypes.add(typeName);
|
|
615
|
+
// Build interface properties
|
|
616
|
+
const interfaceProps = [];
|
|
617
|
+
for (const field of typeInfo.fields) {
|
|
618
|
+
const baseType = getTypeBaseName(field.type);
|
|
619
|
+
if (baseType === 'Query' || baseType === 'Mutation')
|
|
620
|
+
continue;
|
|
621
|
+
const tsType = typeRefToTs(field.type);
|
|
622
|
+
const isNullable = !isRequired(field.type);
|
|
623
|
+
interfaceProps.push({
|
|
624
|
+
name: field.name,
|
|
625
|
+
type: isNullable ? `${tsType} | null` : tsType,
|
|
626
|
+
optional: isNullable,
|
|
627
|
+
});
|
|
628
|
+
// Follow nested OBJECT types
|
|
629
|
+
if (baseType && !generatedTypes.has(baseType) && !skipTypes.has(baseType)) {
|
|
630
|
+
const nestedType = typeRegistry.get(baseType);
|
|
631
|
+
if (nestedType?.kind === 'OBJECT') {
|
|
632
|
+
typesToGenerate.add(baseType);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
sourceFile.addInterface(createInterface(typeName, interfaceProps));
|
|
637
|
+
// Build Select type (no indentation - ts-morph adds it)
|
|
638
|
+
const selectLines = ['{'];
|
|
639
|
+
for (const field of typeInfo.fields) {
|
|
640
|
+
const baseType = getTypeBaseName(field.type);
|
|
641
|
+
if (baseType === 'Query' || baseType === 'Mutation')
|
|
642
|
+
continue;
|
|
643
|
+
const nestedType = baseType ? typeRegistry.get(baseType) : null;
|
|
644
|
+
if (nestedType?.kind === 'OBJECT') {
|
|
645
|
+
selectLines.push(`${field.name}?: boolean | { select?: ${baseType}Select };`);
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
selectLines.push(`${field.name}?: boolean;`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
selectLines.push('}');
|
|
652
|
+
sourceFile.addTypeAlias(createTypeAlias(`${typeName}Select`, selectLines.join('\n')));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// ============================================================================
|
|
656
|
+
// Main Generator (AST-based)
|
|
657
|
+
// ============================================================================
|
|
658
|
+
/**
|
|
659
|
+
* Generate comprehensive input-types.ts file using ts-morph AST
|
|
660
|
+
*/
|
|
661
|
+
export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes) {
|
|
662
|
+
const project = createProject();
|
|
663
|
+
const sourceFile = createSourceFile(project, 'input-types.ts');
|
|
664
|
+
// Add file header
|
|
665
|
+
sourceFile.insertText(0, createFileHeader('GraphQL types for ORM client') + '\n');
|
|
666
|
+
// 1. Scalar filter types
|
|
667
|
+
addScalarFilterTypes(sourceFile);
|
|
668
|
+
// 2. Entity and relation types (if tables provided)
|
|
669
|
+
if (tables && tables.length > 0) {
|
|
670
|
+
const tableByName = new Map(tables.map((table) => [table.name, table]));
|
|
671
|
+
addEntityTypes(sourceFile, tables);
|
|
672
|
+
addRelationHelperTypes(sourceFile);
|
|
673
|
+
addEntityRelationTypes(sourceFile, tables, tableByName);
|
|
674
|
+
addEntityWithRelations(sourceFile, tables);
|
|
675
|
+
addEntitySelectTypes(sourceFile, tables, tableByName);
|
|
676
|
+
// 3. Table filter types
|
|
677
|
+
addTableFilterTypes(sourceFile, tables);
|
|
678
|
+
// 4. OrderBy types
|
|
679
|
+
addOrderByTypes(sourceFile, tables);
|
|
680
|
+
// 5. CRUD input types
|
|
681
|
+
addAllCrudInputTypes(sourceFile, tables);
|
|
682
|
+
}
|
|
683
|
+
// 6. Custom input types from TypeRegistry
|
|
684
|
+
addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes);
|
|
685
|
+
// 7. Payload/return types for custom operations
|
|
686
|
+
if (usedPayloadTypes && usedPayloadTypes.size > 0) {
|
|
687
|
+
const alreadyGeneratedTypes = new Set();
|
|
688
|
+
if (tables) {
|
|
689
|
+
for (const table of tables) {
|
|
690
|
+
const { typeName } = getTableNames(table);
|
|
691
|
+
alreadyGeneratedTypes.add(typeName);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGeneratedTypes);
|
|
695
|
+
}
|
|
696
|
+
return {
|
|
697
|
+
fileName: 'input-types.ts',
|
|
698
|
+
content: getMinimalFormattedOutput(sourceFile),
|
|
699
|
+
};
|
|
700
|
+
}
|