@constructive-io/graphql-codegen 4.9.0 → 4.12.2
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/core/codegen/cli/arg-mapper.d.ts +1 -1
- package/core/codegen/cli/arg-mapper.js +15 -11
- package/core/codegen/cli/custom-command-generator.js +4 -3
- package/core/codegen/cli/docs-generator.d.ts +6 -5
- package/core/codegen/cli/docs-generator.js +167 -64
- package/core/codegen/cli/table-command-generator.d.ts +15 -0
- package/core/codegen/cli/table-command-generator.js +20 -1
- package/core/codegen/docs-utils.d.ts +26 -1
- package/core/codegen/docs-utils.js +105 -0
- package/core/codegen/orm/input-types-generator.js +112 -14
- package/core/codegen/orm/model-generator.js +8 -0
- package/core/codegen/orm/select-types.d.ts +4 -2
- package/core/codegen/scalars.js +8 -0
- package/core/codegen/templates/cli-entry.ts +2 -2
- package/core/codegen/templates/cli-utils.ts +28 -0
- package/core/codegen/templates/query-builder.ts +28 -5
- package/core/codegen/templates/select-types.ts +4 -2
- package/core/generate.js +14 -4
- package/esm/core/codegen/cli/arg-mapper.d.ts +1 -1
- package/esm/core/codegen/cli/arg-mapper.js +15 -11
- package/esm/core/codegen/cli/custom-command-generator.js +4 -3
- package/esm/core/codegen/cli/docs-generator.d.ts +6 -5
- package/esm/core/codegen/cli/docs-generator.js +168 -65
- package/esm/core/codegen/cli/table-command-generator.d.ts +15 -0
- package/esm/core/codegen/cli/table-command-generator.js +20 -3
- package/esm/core/codegen/docs-utils.d.ts +26 -1
- package/esm/core/codegen/docs-utils.js +102 -0
- package/esm/core/codegen/orm/input-types-generator.js +112 -14
- package/esm/core/codegen/orm/model-generator.js +8 -0
- package/esm/core/codegen/orm/select-types.d.ts +4 -2
- package/esm/core/codegen/scalars.js +8 -0
- package/esm/core/generate.js +14 -4
- package/package.json +11 -11
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DocsConfig } from '../../types/config';
|
|
2
|
-
import type { CleanField, CleanOperation, CleanTable } from '../../types/schema';
|
|
2
|
+
import type { CleanArgument, CleanField, CleanOperation, CleanTable, TypeRegistry } from '../../types/schema';
|
|
3
3
|
export interface GeneratedDocFile {
|
|
4
4
|
fileName: string;
|
|
5
5
|
content: string;
|
|
@@ -36,6 +36,31 @@ export declare function resolveDocsConfig(docs: DocsConfig | boolean | undefined
|
|
|
36
36
|
export declare function formatArgType(arg: CleanOperation['args'][number]): string;
|
|
37
37
|
export declare function formatTypeRef(t: CleanOperation['args'][number]['type']): string;
|
|
38
38
|
export declare function getEditableFields(table: CleanTable): CleanField[];
|
|
39
|
+
/**
|
|
40
|
+
* Represents a flattened argument for docs/skills generation.
|
|
41
|
+
* INPUT_OBJECT args are expanded to dot-notation fields.
|
|
42
|
+
*/
|
|
43
|
+
export interface FlattenedArg {
|
|
44
|
+
/** Flag name for CLI usage, e.g. 'input.email' or 'clientMutationId' */
|
|
45
|
+
flag: string;
|
|
46
|
+
/** Human-readable type string */
|
|
47
|
+
type: string;
|
|
48
|
+
/** Whether the argument is required */
|
|
49
|
+
required: boolean;
|
|
50
|
+
/** Description from schema */
|
|
51
|
+
description?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Strip internal type prefixes for cleaner docs display.
|
|
55
|
+
* e.g. 'ConstructiveInternalTypeEmail' -> 'Email'
|
|
56
|
+
*/
|
|
57
|
+
export declare function cleanTypeName(name: string): string;
|
|
58
|
+
export declare function flattenArgs(args: CleanArgument[], registry?: TypeRegistry): FlattenedArg[];
|
|
59
|
+
/**
|
|
60
|
+
* Build CLI flags string from flattened args.
|
|
61
|
+
* e.g. '--input.email <value> --input.password <value>'
|
|
62
|
+
*/
|
|
63
|
+
export declare function flattenedArgsToFlags(flatArgs: FlattenedArg[]): string;
|
|
39
64
|
export declare function gqlTypeToJsonSchemaType(gqlType: string): string;
|
|
40
65
|
export declare function buildSkillFile(skill: SkillDefinition, referenceNames?: string[]): string;
|
|
41
66
|
export declare function buildSkillReference(ref: SkillReferenceDefinition): string;
|
|
@@ -6,6 +6,9 @@ exports.resolveDocsConfig = resolveDocsConfig;
|
|
|
6
6
|
exports.formatArgType = formatArgType;
|
|
7
7
|
exports.formatTypeRef = formatTypeRef;
|
|
8
8
|
exports.getEditableFields = getEditableFields;
|
|
9
|
+
exports.cleanTypeName = cleanTypeName;
|
|
10
|
+
exports.flattenArgs = flattenArgs;
|
|
11
|
+
exports.flattenedArgsToFlags = flattenedArgsToFlags;
|
|
9
12
|
exports.gqlTypeToJsonSchemaType = gqlTypeToJsonSchemaType;
|
|
10
13
|
exports.buildSkillFile = buildSkillFile;
|
|
11
14
|
exports.buildSkillReference = buildSkillReference;
|
|
@@ -78,6 +81,108 @@ function getEditableFields(table) {
|
|
|
78
81
|
f.name !== 'createdAt' &&
|
|
79
82
|
f.name !== 'updatedAt');
|
|
80
83
|
}
|
|
84
|
+
function unwrapNonNull(typeRef) {
|
|
85
|
+
if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
|
|
86
|
+
return { inner: typeRef.ofType, required: true };
|
|
87
|
+
}
|
|
88
|
+
return { inner: typeRef, required: false };
|
|
89
|
+
}
|
|
90
|
+
function resolveBaseType(typeRef) {
|
|
91
|
+
if ((typeRef.kind === 'NON_NULL' || typeRef.kind === 'LIST') && typeRef.ofType) {
|
|
92
|
+
return resolveBaseType(typeRef.ofType);
|
|
93
|
+
}
|
|
94
|
+
return typeRef;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Strip internal type prefixes for cleaner docs display.
|
|
98
|
+
* e.g. 'ConstructiveInternalTypeEmail' -> 'Email'
|
|
99
|
+
*/
|
|
100
|
+
function cleanTypeName(name) {
|
|
101
|
+
if (name.startsWith('ConstructiveInternalType')) {
|
|
102
|
+
return name.slice('ConstructiveInternalType'.length);
|
|
103
|
+
}
|
|
104
|
+
return name;
|
|
105
|
+
}
|
|
106
|
+
function getScalarTypeName(typeRef) {
|
|
107
|
+
const base = resolveBaseType(typeRef);
|
|
108
|
+
return cleanTypeName(base.name ?? 'String');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Flatten operation args for docs/skills, expanding INPUT_OBJECT types
|
|
112
|
+
* to dot-notation fields (e.g. input.email, input.password).
|
|
113
|
+
* Mirrors the logic in arg-mapper.ts buildQuestionsArray.
|
|
114
|
+
*/
|
|
115
|
+
/**
|
|
116
|
+
* Resolve inputFields for an INPUT_OBJECT type.
|
|
117
|
+
* Checks the CleanTypeRef first, then falls back to the TypeRegistry.
|
|
118
|
+
*/
|
|
119
|
+
function resolveInputFields(typeRef, registry) {
|
|
120
|
+
if (typeRef.inputFields && typeRef.inputFields.length > 0) {
|
|
121
|
+
return typeRef.inputFields;
|
|
122
|
+
}
|
|
123
|
+
if (registry && typeRef.name) {
|
|
124
|
+
const resolved = registry.get(typeRef.name);
|
|
125
|
+
if (resolved?.kind === 'INPUT_OBJECT' && resolved.inputFields && resolved.inputFields.length > 0) {
|
|
126
|
+
return resolved.inputFields;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
function flattenArgs(args, registry) {
|
|
132
|
+
const result = [];
|
|
133
|
+
for (const arg of args) {
|
|
134
|
+
const { inner, required } = unwrapNonNull(arg.type);
|
|
135
|
+
const base = resolveBaseType(arg.type);
|
|
136
|
+
// Try to resolve inputFields from the inner type first (unwrapped NON_NULL)
|
|
137
|
+
const innerFields = inner.kind === 'INPUT_OBJECT'
|
|
138
|
+
? resolveInputFields(inner, registry)
|
|
139
|
+
: undefined;
|
|
140
|
+
if (innerFields) {
|
|
141
|
+
for (const field of innerFields) {
|
|
142
|
+
const { required: fieldRequired } = unwrapNonNull(field.type);
|
|
143
|
+
result.push({
|
|
144
|
+
flag: `${arg.name}.${field.name}`,
|
|
145
|
+
type: getScalarTypeName(field.type),
|
|
146
|
+
required: required && fieldRequired,
|
|
147
|
+
description: field.description,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Try the base type (unwrapped LIST+NON_NULL)
|
|
153
|
+
const baseFields = base.kind === 'INPUT_OBJECT'
|
|
154
|
+
? resolveInputFields(base, registry)
|
|
155
|
+
: undefined;
|
|
156
|
+
if (baseFields) {
|
|
157
|
+
for (const field of baseFields) {
|
|
158
|
+
const { required: fieldRequired } = unwrapNonNull(field.type);
|
|
159
|
+
result.push({
|
|
160
|
+
flag: `${arg.name}.${field.name}`,
|
|
161
|
+
type: getScalarTypeName(field.type),
|
|
162
|
+
required: required && fieldRequired,
|
|
163
|
+
description: field.description,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
result.push({
|
|
169
|
+
flag: arg.name,
|
|
170
|
+
type: getScalarTypeName(arg.type),
|
|
171
|
+
required,
|
|
172
|
+
description: arg.description,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Build CLI flags string from flattened args.
|
|
181
|
+
* e.g. '--input.email <value> --input.password <value>'
|
|
182
|
+
*/
|
|
183
|
+
function flattenedArgsToFlags(flatArgs) {
|
|
184
|
+
return flatArgs.map((a) => `--${a.flag} <value>`).join(' ');
|
|
185
|
+
}
|
|
81
186
|
function gqlTypeToJsonSchemaType(gqlType) {
|
|
82
187
|
switch (gqlType) {
|
|
83
188
|
case 'Int':
|
|
@@ -252,6 +252,11 @@ const SCALAR_FILTER_CONFIGS = [
|
|
|
252
252
|
operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'],
|
|
253
253
|
},
|
|
254
254
|
{ name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
|
|
255
|
+
// VectorFilter: equality/distinct operators for vector columns on Filter types.
|
|
256
|
+
// Similarity search uses condition types (embeddingNearby), not filters, but
|
|
257
|
+
// connection-filter may still generate a filter for vector columns. This ensures
|
|
258
|
+
// the generated type uses number[] rather than being silently omitted.
|
|
259
|
+
{ name: 'VectorFilter', tsType: 'number[]', operators: ['equality', 'distinct'] },
|
|
255
260
|
// List filters (for array fields like string[], int[], uuid[])
|
|
256
261
|
{
|
|
257
262
|
name: 'StringListFilter',
|
|
@@ -748,10 +753,15 @@ function generateTableFilterTypes(tables) {
|
|
|
748
753
|
// ============================================================================
|
|
749
754
|
/**
|
|
750
755
|
* Build properties for a table condition interface
|
|
751
|
-
* Condition types are simpler than Filter types - they use direct value equality
|
|
756
|
+
* Condition types are simpler than Filter types - they use direct value equality.
|
|
757
|
+
*
|
|
758
|
+
* Also merges any extra fields from the GraphQL schema's condition type
|
|
759
|
+
* (e.g., plugin-injected fields like vectorEmbedding from VectorSearchPlugin)
|
|
760
|
+
* that are not derived from the table's own columns.
|
|
752
761
|
*/
|
|
753
|
-
function buildTableConditionProperties(table) {
|
|
762
|
+
function buildTableConditionProperties(table, typeRegistry) {
|
|
754
763
|
const properties = [];
|
|
764
|
+
const generatedFieldNames = new Set();
|
|
755
765
|
for (const field of table.fields) {
|
|
756
766
|
const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
|
|
757
767
|
if ((0, utils_1.isRelationField)(field.name, table))
|
|
@@ -763,17 +773,38 @@ function buildTableConditionProperties(table) {
|
|
|
763
773
|
type: `${tsType} | null`,
|
|
764
774
|
optional: true,
|
|
765
775
|
});
|
|
776
|
+
generatedFieldNames.add(field.name);
|
|
777
|
+
}
|
|
778
|
+
// Merge any additional fields from the schema's condition type
|
|
779
|
+
// (e.g., plugin-added fields like vectorEmbedding from VectorSearchPlugin)
|
|
780
|
+
if (typeRegistry) {
|
|
781
|
+
const conditionTypeName = (0, utils_1.getConditionTypeName)(table);
|
|
782
|
+
const conditionType = typeRegistry.get(conditionTypeName);
|
|
783
|
+
if (conditionType?.kind === 'INPUT_OBJECT' &&
|
|
784
|
+
conditionType.inputFields) {
|
|
785
|
+
for (const field of conditionType.inputFields) {
|
|
786
|
+
if (generatedFieldNames.has(field.name))
|
|
787
|
+
continue;
|
|
788
|
+
const tsType = typeRefToTs(field.type);
|
|
789
|
+
properties.push({
|
|
790
|
+
name: field.name,
|
|
791
|
+
type: tsType,
|
|
792
|
+
optional: true,
|
|
793
|
+
description: (0, utils_1.stripSmartComments)(field.description, true),
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
766
797
|
}
|
|
767
798
|
return properties;
|
|
768
799
|
}
|
|
769
800
|
/**
|
|
770
801
|
* Generate table condition type statements
|
|
771
802
|
*/
|
|
772
|
-
function generateTableConditionTypes(tables) {
|
|
803
|
+
function generateTableConditionTypes(tables, typeRegistry) {
|
|
773
804
|
const statements = [];
|
|
774
805
|
for (const table of tables) {
|
|
775
806
|
const conditionName = (0, utils_1.getConditionTypeName)(table);
|
|
776
|
-
statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table)));
|
|
807
|
+
statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table, typeRegistry)));
|
|
777
808
|
}
|
|
778
809
|
if (statements.length > 0) {
|
|
779
810
|
addSectionComment(statements, 'Table Condition Types');
|
|
@@ -784,9 +815,13 @@ function generateTableConditionTypes(tables) {
|
|
|
784
815
|
// OrderBy Types Generator (AST-based)
|
|
785
816
|
// ============================================================================
|
|
786
817
|
/**
|
|
787
|
-
* Build OrderBy union type values
|
|
818
|
+
* Build OrderBy union type values.
|
|
819
|
+
*
|
|
820
|
+
* Also merges any extra values from the GraphQL schema's orderBy enum
|
|
821
|
+
* (e.g., plugin-injected values like EMBEDDING_DISTANCE_ASC/DESC
|
|
822
|
+
* from VectorSearchPlugin).
|
|
788
823
|
*/
|
|
789
|
-
function buildOrderByValues(table) {
|
|
824
|
+
function buildOrderByValues(table, typeRegistry) {
|
|
790
825
|
const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
|
|
791
826
|
for (const field of table.fields) {
|
|
792
827
|
if ((0, utils_1.isRelationField)(field.name, table))
|
|
@@ -795,16 +830,30 @@ function buildOrderByValues(table) {
|
|
|
795
830
|
values.push(`${upperSnake}_ASC`);
|
|
796
831
|
values.push(`${upperSnake}_DESC`);
|
|
797
832
|
}
|
|
833
|
+
// Merge any additional values from the schema's orderBy enum type
|
|
834
|
+
// (e.g., plugin-added values like EMBEDDING_DISTANCE_ASC/DESC)
|
|
835
|
+
if (typeRegistry) {
|
|
836
|
+
const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
|
|
837
|
+
const orderByType = typeRegistry.get(orderByTypeName);
|
|
838
|
+
if (orderByType?.kind === 'ENUM' && orderByType.enumValues) {
|
|
839
|
+
const existingValues = new Set(values);
|
|
840
|
+
for (const value of orderByType.enumValues) {
|
|
841
|
+
if (!existingValues.has(value)) {
|
|
842
|
+
values.push(value);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
798
847
|
return values;
|
|
799
848
|
}
|
|
800
849
|
/**
|
|
801
850
|
* Generate OrderBy type statements
|
|
802
851
|
*/
|
|
803
|
-
function generateOrderByTypes(tables) {
|
|
852
|
+
function generateOrderByTypes(tables, typeRegistry) {
|
|
804
853
|
const statements = [];
|
|
805
854
|
for (const table of tables) {
|
|
806
855
|
const enumName = (0, utils_1.getOrderByTypeName)(table);
|
|
807
|
-
const values = buildOrderByValues(table);
|
|
856
|
+
const values = buildOrderByValues(table, typeRegistry);
|
|
808
857
|
const unionType = createStringLiteralUnion(values);
|
|
809
858
|
const typeAlias = t.tsTypeAliasDeclaration(t.identifier(enumName), null, unionType);
|
|
810
859
|
statements.push(t.exportNamedDeclaration(typeAlias));
|
|
@@ -1055,11 +1104,12 @@ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes,
|
|
|
1055
1104
|
optional,
|
|
1056
1105
|
description: (0, utils_1.stripSmartComments)(field.description, comments),
|
|
1057
1106
|
});
|
|
1058
|
-
// Follow nested Input
|
|
1107
|
+
// Follow nested types (Input objects, Enums, etc.) that exist in the registry
|
|
1059
1108
|
const baseType = (0, type_resolver_1.getTypeBaseName)(field.type);
|
|
1060
1109
|
if (baseType &&
|
|
1061
|
-
|
|
1062
|
-
!generatedTypes.has(baseType)
|
|
1110
|
+
!scalars_1.SCALAR_NAMES.has(baseType) &&
|
|
1111
|
+
!generatedTypes.has(baseType) &&
|
|
1112
|
+
typeRegistry.has(baseType)) {
|
|
1063
1113
|
typesToGenerate.add(baseType);
|
|
1064
1114
|
}
|
|
1065
1115
|
}
|
|
@@ -1243,6 +1293,42 @@ function generateConnectionFieldsMap(tables, tableByName) {
|
|
|
1243
1293
|
return statements;
|
|
1244
1294
|
}
|
|
1245
1295
|
// ============================================================================
|
|
1296
|
+
// Plugin-Injected Type Collector
|
|
1297
|
+
// ============================================================================
|
|
1298
|
+
/**
|
|
1299
|
+
* Collect extra input type names referenced by plugin-injected condition fields.
|
|
1300
|
+
*
|
|
1301
|
+
* When plugins (like VectorSearchPlugin) inject fields into condition types,
|
|
1302
|
+
* they reference types (like VectorNearbyInput, VectorMetric) that also need
|
|
1303
|
+
* to be generated. This function discovers those types by comparing the
|
|
1304
|
+
* schema's condition type fields against the table's own columns.
|
|
1305
|
+
*/
|
|
1306
|
+
function collectConditionExtraInputTypes(tables, typeRegistry) {
|
|
1307
|
+
const extraTypes = new Set();
|
|
1308
|
+
for (const table of tables) {
|
|
1309
|
+
const conditionTypeName = (0, utils_1.getConditionTypeName)(table);
|
|
1310
|
+
const conditionType = typeRegistry.get(conditionTypeName);
|
|
1311
|
+
if (!conditionType ||
|
|
1312
|
+
conditionType.kind !== 'INPUT_OBJECT' ||
|
|
1313
|
+
!conditionType.inputFields) {
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
const tableFieldNames = new Set(table.fields
|
|
1317
|
+
.filter((f) => !(0, utils_1.isRelationField)(f.name, table))
|
|
1318
|
+
.map((f) => f.name));
|
|
1319
|
+
for (const field of conditionType.inputFields) {
|
|
1320
|
+
if (tableFieldNames.has(field.name))
|
|
1321
|
+
continue;
|
|
1322
|
+
// Collect the base type name of this extra field
|
|
1323
|
+
const baseName = (0, type_resolver_1.getTypeBaseName)(field.type);
|
|
1324
|
+
if (baseName && !scalars_1.SCALAR_NAMES.has(baseName)) {
|
|
1325
|
+
extraTypes.add(baseName);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return extraTypes;
|
|
1330
|
+
}
|
|
1331
|
+
// ============================================================================
|
|
1246
1332
|
// Main Generator (AST-based)
|
|
1247
1333
|
// ============================================================================
|
|
1248
1334
|
/**
|
|
@@ -1273,9 +1359,13 @@ function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloa
|
|
|
1273
1359
|
// 4. Table filter types
|
|
1274
1360
|
statements.push(...generateTableFilterTypes(tablesList));
|
|
1275
1361
|
// 4b. Table condition types (simple equality filter)
|
|
1276
|
-
|
|
1362
|
+
// Pass typeRegistry to merge plugin-injected condition fields
|
|
1363
|
+
// (e.g., vectorEmbedding from VectorSearchPlugin)
|
|
1364
|
+
statements.push(...generateTableConditionTypes(tablesList, typeRegistry));
|
|
1277
1365
|
// 5. OrderBy types
|
|
1278
|
-
|
|
1366
|
+
// Pass typeRegistry to merge plugin-injected orderBy values
|
|
1367
|
+
// (e.g., EMBEDDING_DISTANCE_ASC/DESC from VectorSearchPlugin)
|
|
1368
|
+
statements.push(...generateOrderByTypes(tablesList, typeRegistry));
|
|
1279
1369
|
// 6. CRUD input types
|
|
1280
1370
|
statements.push(...generateAllCrudInputTypes(tablesList, typeRegistry));
|
|
1281
1371
|
}
|
|
@@ -1283,8 +1373,16 @@ function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloa
|
|
|
1283
1373
|
// Always emit this export so generated model/custom-op imports stay valid.
|
|
1284
1374
|
statements.push(...generateConnectionFieldsMap(tablesList, tableByName));
|
|
1285
1375
|
// 7. Custom input types from TypeRegistry
|
|
1376
|
+
// Also include any extra types referenced by plugin-injected condition fields
|
|
1377
|
+
const mergedUsedInputTypes = new Set(usedInputTypes);
|
|
1378
|
+
if (hasTables) {
|
|
1379
|
+
const conditionExtraTypes = collectConditionExtraInputTypes(tablesList, typeRegistry);
|
|
1380
|
+
for (const typeName of conditionExtraTypes) {
|
|
1381
|
+
mergedUsedInputTypes.add(typeName);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1286
1384
|
const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
|
|
1287
|
-
statements.push(...generateCustomInputTypes(typeRegistry,
|
|
1385
|
+
statements.push(...generateCustomInputTypes(typeRegistry, mergedUsedInputTypes, tableCrudTypes, comments));
|
|
1288
1386
|
// 8. Payload/return types for custom operations
|
|
1289
1387
|
if (usedPayloadTypes && usedPayloadTypes.size > 0) {
|
|
1290
1388
|
const alreadyGeneratedTypes = new Set();
|
|
@@ -111,6 +111,7 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
111
111
|
const selectTypeName = `${typeName}Select`;
|
|
112
112
|
const relationTypeName = `${typeName}WithRelations`;
|
|
113
113
|
const whereTypeName = (0, utils_1.getFilterTypeName)(table);
|
|
114
|
+
const conditionTypeName = `${typeName}Condition`;
|
|
114
115
|
const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
|
|
115
116
|
const createInputTypeName = `Create${typeName}Input`;
|
|
116
117
|
const updateInputTypeName = `Update${typeName}Input`;
|
|
@@ -150,6 +151,7 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
150
151
|
relationTypeName,
|
|
151
152
|
selectTypeName,
|
|
152
153
|
whereTypeName,
|
|
154
|
+
conditionTypeName,
|
|
153
155
|
orderByTypeName,
|
|
154
156
|
createInputTypeName,
|
|
155
157
|
updateInputTypeName,
|
|
@@ -171,6 +173,7 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
171
173
|
const argsType = (sel) => t.tsTypeReference(t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation([
|
|
172
174
|
sel,
|
|
173
175
|
t.tsTypeReference(t.identifier(whereTypeName)),
|
|
176
|
+
t.tsTypeReference(t.identifier(conditionTypeName)),
|
|
174
177
|
t.tsTypeReference(t.identifier(orderByTypeName)),
|
|
175
178
|
]));
|
|
176
179
|
const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
|
|
@@ -196,6 +199,7 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
196
199
|
selectExpr,
|
|
197
200
|
t.objectExpression([
|
|
198
201
|
t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
|
|
202
|
+
t.objectProperty(t.identifier('condition'), t.optionalMemberExpression(t.identifier('args'), t.identifier('condition'), false, true)),
|
|
199
203
|
t.objectProperty(t.identifier('orderBy'), t.tsAsExpression(t.optionalMemberExpression(t.identifier('args'), t.identifier('orderBy'), false, true), t.tsUnionType([
|
|
200
204
|
t.tsArrayType(t.tsStringKeyword()),
|
|
201
205
|
t.tsUndefinedKeyword(),
|
|
@@ -209,6 +213,7 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
209
213
|
t.stringLiteral(whereTypeName),
|
|
210
214
|
t.stringLiteral(orderByTypeName),
|
|
211
215
|
t.identifier('connectionFieldsMap'),
|
|
216
|
+
t.stringLiteral(conditionTypeName),
|
|
212
217
|
];
|
|
213
218
|
classBody.push(createClassMethod('findMany', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindManyDocument', bodyArgs, 'query', typeName, pluralQueryName)));
|
|
214
219
|
}
|
|
@@ -217,6 +222,7 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
217
222
|
const argsType = (sel) => t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
|
|
218
223
|
sel,
|
|
219
224
|
t.tsTypeReference(t.identifier(whereTypeName)),
|
|
225
|
+
t.tsTypeReference(t.identifier(conditionTypeName)),
|
|
220
226
|
]));
|
|
221
227
|
const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
|
|
222
228
|
t.tsTypeLiteral([
|
|
@@ -241,9 +247,11 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
241
247
|
selectExpr,
|
|
242
248
|
t.objectExpression([
|
|
243
249
|
t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
|
|
250
|
+
t.objectProperty(t.identifier('condition'), t.optionalMemberExpression(t.identifier('args'), t.identifier('condition'), false, true)),
|
|
244
251
|
]),
|
|
245
252
|
t.stringLiteral(whereTypeName),
|
|
246
253
|
t.identifier('connectionFieldsMap'),
|
|
254
|
+
t.stringLiteral(conditionTypeName),
|
|
247
255
|
];
|
|
248
256
|
classBody.push(createClassMethod('findFirst', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindFirstDocument', bodyArgs, 'query', typeName, pluralQueryName)));
|
|
249
257
|
}
|
|
@@ -150,9 +150,10 @@ export interface PageInfo {
|
|
|
150
150
|
/**
|
|
151
151
|
* Arguments for findMany operations
|
|
152
152
|
*/
|
|
153
|
-
export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
|
|
153
|
+
export interface FindManyArgs<TSelect, TWhere, TCondition, TOrderBy> {
|
|
154
154
|
select?: TSelect;
|
|
155
155
|
where?: TWhere;
|
|
156
|
+
condition?: TCondition;
|
|
156
157
|
orderBy?: TOrderBy[];
|
|
157
158
|
first?: number;
|
|
158
159
|
last?: number;
|
|
@@ -163,9 +164,10 @@ export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
|
|
|
163
164
|
/**
|
|
164
165
|
* Arguments for findFirst/findUnique operations
|
|
165
166
|
*/
|
|
166
|
-
export interface FindFirstArgs<TSelect, TWhere> {
|
|
167
|
+
export interface FindFirstArgs<TSelect, TWhere, TCondition> {
|
|
167
168
|
select?: TSelect;
|
|
168
169
|
where?: TWhere;
|
|
170
|
+
condition?: TCondition;
|
|
169
171
|
}
|
|
170
172
|
/**
|
|
171
173
|
* Arguments for create operations
|
package/core/codegen/scalars.js
CHANGED
|
@@ -38,6 +38,8 @@ exports.SCALAR_TS_MAP = {
|
|
|
38
38
|
MacAddr: 'string',
|
|
39
39
|
TsVector: 'string',
|
|
40
40
|
TsQuery: 'string',
|
|
41
|
+
// Vector types (pgvector) — serialized as [Float] in GraphQL
|
|
42
|
+
Vector: 'number[]',
|
|
41
43
|
// File upload
|
|
42
44
|
Upload: 'File',
|
|
43
45
|
};
|
|
@@ -56,6 +58,12 @@ exports.SCALAR_FILTER_MAP = {
|
|
|
56
58
|
BigFloat: 'BigFloatFilter',
|
|
57
59
|
BitString: 'BitStringFilter',
|
|
58
60
|
InternetAddress: 'InternetAddressFilter',
|
|
61
|
+
// VectorFilter provides equality/distinct operators (isNull, equalTo, etc.) for vector
|
|
62
|
+
// columns on Filter types. While similarity search is done via condition types
|
|
63
|
+
// (e.g., embeddingNearby on ContactCondition), postgraphile-plugin-connection-filter
|
|
64
|
+
// may still auto-generate a filter type for vector columns. Without this mapping,
|
|
65
|
+
// those fields would be silently omitted from the generated SDK.
|
|
66
|
+
Vector: 'VectorFilter',
|
|
59
67
|
FullText: 'FullTextFilter',
|
|
60
68
|
Interval: 'StringFilter',
|
|
61
69
|
};
|
|
@@ -17,9 +17,9 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
|
17
17
|
process.exit(0);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// Check for --tty false to enable non-interactive mode (noTty)
|
|
20
|
+
// Check for --tty false or --no-tty to enable non-interactive mode (noTty)
|
|
21
21
|
const ttyIdx = process.argv.indexOf('--tty');
|
|
22
|
-
const noTty = ttyIdx !== -1 && process.argv[ttyIdx + 1] === 'false';
|
|
22
|
+
const noTty = (ttyIdx !== -1 && process.argv[ttyIdx + 1] === 'false') || process.argv.includes('--no-tty');
|
|
23
23
|
|
|
24
24
|
const options: Partial<CLIOptions> = {
|
|
25
25
|
noTty,
|
|
@@ -145,6 +145,34 @@ export function parseMutationInput(
|
|
|
145
145
|
return answers;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Reconstruct nested objects from dot-notation CLI answers.
|
|
150
|
+
* When INPUT_OBJECT args are flattened to dot-notation questions
|
|
151
|
+
* (e.g. `--input.email foo --input.password bar`), this function
|
|
152
|
+
* rebuilds the nested structure expected by the ORM:
|
|
153
|
+
*
|
|
154
|
+
* { 'input.email': 'foo', 'input.password': 'bar' }
|
|
155
|
+
* → { input: { email: 'foo', password: 'bar' } }
|
|
156
|
+
*
|
|
157
|
+
* Non-dotted keys are passed through unchanged.
|
|
158
|
+
* Uses `nested-obj` for safe nested property setting.
|
|
159
|
+
*/
|
|
160
|
+
export function unflattenDotNotation(
|
|
161
|
+
answers: Record<string, unknown>,
|
|
162
|
+
): Record<string, unknown> {
|
|
163
|
+
const result: Record<string, unknown> = {};
|
|
164
|
+
|
|
165
|
+
for (const [key, value] of Object.entries(answers)) {
|
|
166
|
+
if (key.includes('.')) {
|
|
167
|
+
objectPath.set(result, key, value);
|
|
168
|
+
} else {
|
|
169
|
+
result[key] = value;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
148
176
|
/**
|
|
149
177
|
* Build a select object from a comma-separated list of dot-notation paths.
|
|
150
178
|
* Uses `nested-obj` to parse paths like 'clientMutationId,result.accessToken,result.userId'
|
|
@@ -201,12 +201,13 @@ export function buildSelections(
|
|
|
201
201
|
// Document Builders
|
|
202
202
|
// ============================================================================
|
|
203
203
|
|
|
204
|
-
export function buildFindManyDocument<TSelect, TWhere>(
|
|
204
|
+
export function buildFindManyDocument<TSelect, TWhere, TCondition>(
|
|
205
205
|
operationName: string,
|
|
206
206
|
queryField: string,
|
|
207
207
|
select: TSelect,
|
|
208
208
|
args: {
|
|
209
209
|
where?: TWhere;
|
|
210
|
+
condition?: TCondition;
|
|
210
211
|
orderBy?: string[];
|
|
211
212
|
first?: number;
|
|
212
213
|
last?: number;
|
|
@@ -217,6 +218,7 @@ export function buildFindManyDocument<TSelect, TWhere>(
|
|
|
217
218
|
filterTypeName: string,
|
|
218
219
|
orderByTypeName: string,
|
|
219
220
|
connectionFieldsMap?: Record<string, Record<string, string>>,
|
|
221
|
+
conditionTypeName?: string,
|
|
220
222
|
): { document: string; variables: Record<string, unknown> } {
|
|
221
223
|
const selections = select
|
|
222
224
|
? buildSelections(
|
|
@@ -230,6 +232,16 @@ export function buildFindManyDocument<TSelect, TWhere>(
|
|
|
230
232
|
const queryArgs: ArgumentNode[] = [];
|
|
231
233
|
const variables: Record<string, unknown> = {};
|
|
232
234
|
|
|
235
|
+
addVariable(
|
|
236
|
+
{
|
|
237
|
+
varName: 'condition',
|
|
238
|
+
typeName: conditionTypeName,
|
|
239
|
+
value: args.condition,
|
|
240
|
+
},
|
|
241
|
+
variableDefinitions,
|
|
242
|
+
queryArgs,
|
|
243
|
+
variables,
|
|
244
|
+
);
|
|
233
245
|
addVariable(
|
|
234
246
|
{
|
|
235
247
|
varName: 'where',
|
|
@@ -308,13 +320,14 @@ export function buildFindManyDocument<TSelect, TWhere>(
|
|
|
308
320
|
return { document: print(document), variables };
|
|
309
321
|
}
|
|
310
322
|
|
|
311
|
-
export function buildFindFirstDocument<TSelect, TWhere>(
|
|
323
|
+
export function buildFindFirstDocument<TSelect, TWhere, TCondition>(
|
|
312
324
|
operationName: string,
|
|
313
325
|
queryField: string,
|
|
314
326
|
select: TSelect,
|
|
315
|
-
args: { where?: TWhere },
|
|
327
|
+
args: { where?: TWhere; condition?: TCondition },
|
|
316
328
|
filterTypeName: string,
|
|
317
329
|
connectionFieldsMap?: Record<string, Record<string, string>>,
|
|
330
|
+
conditionTypeName?: string,
|
|
318
331
|
): { document: string; variables: Record<string, unknown> } {
|
|
319
332
|
const selections = select
|
|
320
333
|
? buildSelections(
|
|
@@ -335,6 +348,16 @@ export function buildFindFirstDocument<TSelect, TWhere>(
|
|
|
335
348
|
queryArgs,
|
|
336
349
|
variables,
|
|
337
350
|
);
|
|
351
|
+
addVariable(
|
|
352
|
+
{
|
|
353
|
+
varName: 'condition',
|
|
354
|
+
typeName: conditionTypeName,
|
|
355
|
+
value: args.condition,
|
|
356
|
+
},
|
|
357
|
+
variableDefinitions,
|
|
358
|
+
queryArgs,
|
|
359
|
+
variables,
|
|
360
|
+
);
|
|
338
361
|
addVariable(
|
|
339
362
|
{
|
|
340
363
|
varName: 'where',
|
|
@@ -799,7 +822,7 @@ function buildConnectionSelections(nodeSelections: FieldNode[]): FieldNode[] {
|
|
|
799
822
|
interface VariableSpec {
|
|
800
823
|
varName: string;
|
|
801
824
|
argName?: string;
|
|
802
|
-
typeName
|
|
825
|
+
typeName?: string;
|
|
803
826
|
value: unknown;
|
|
804
827
|
}
|
|
805
828
|
|
|
@@ -850,7 +873,7 @@ function addVariable(
|
|
|
850
873
|
args: ArgumentNode[],
|
|
851
874
|
variables: Record<string, unknown>,
|
|
852
875
|
): void {
|
|
853
|
-
if (spec.value === undefined) return;
|
|
876
|
+
if (spec.value === undefined || !spec.typeName) return;
|
|
854
877
|
|
|
855
878
|
definitions.push(
|
|
856
879
|
t.variableDefinition({
|
|
@@ -21,9 +21,10 @@ export interface PageInfo {
|
|
|
21
21
|
endCursor?: string | null;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
|
|
24
|
+
export interface FindManyArgs<TSelect, TWhere, TCondition, TOrderBy> {
|
|
25
25
|
select?: TSelect;
|
|
26
26
|
where?: TWhere;
|
|
27
|
+
condition?: TCondition;
|
|
27
28
|
orderBy?: TOrderBy[];
|
|
28
29
|
first?: number;
|
|
29
30
|
last?: number;
|
|
@@ -32,9 +33,10 @@ export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
|
|
|
32
33
|
offset?: number;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
export interface FindFirstArgs<TSelect, TWhere> {
|
|
36
|
+
export interface FindFirstArgs<TSelect, TWhere, TCondition> {
|
|
36
37
|
select?: TSelect;
|
|
37
38
|
where?: TWhere;
|
|
39
|
+
condition?: TCondition;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export interface CreateArgs<TSelect, TData> {
|