@constructive-io/graphql-codegen 4.8.1 → 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 +29 -3
- 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 +29 -5
- 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
|
@@ -67,6 +67,22 @@ function getTsTypeForField(field) {
|
|
|
67
67
|
return t.tsStringKeyword();
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Maps a GraphQL scalar type to the appropriate inquirerer question type.
|
|
72
|
+
* Used by table CRUD commands to generate semantic prompts.
|
|
73
|
+
*/
|
|
74
|
+
function getQuestionTypeForField(field) {
|
|
75
|
+
const gqlType = field.type.gqlType.replace(/!/g, '');
|
|
76
|
+
switch (gqlType) {
|
|
77
|
+
case 'Boolean':
|
|
78
|
+
return 'boolean';
|
|
79
|
+
case 'JSON':
|
|
80
|
+
case 'GeoJSON':
|
|
81
|
+
return 'json';
|
|
82
|
+
default:
|
|
83
|
+
return 'text';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
70
86
|
function buildFieldSchemaObject(table) {
|
|
71
87
|
const fields = getScalarFields(table);
|
|
72
88
|
return t.objectExpression(fields.map((f) => {
|
|
@@ -214,7 +230,7 @@ function buildGetHandler(table, targetName) {
|
|
|
214
230
|
* The CreateXInput has an inner field (e.g. "database" of type DatabaseInput)
|
|
215
231
|
* that contains the actual field definitions.
|
|
216
232
|
*/
|
|
217
|
-
function resolveInnerInputType(inputTypeName, typeRegistry) {
|
|
233
|
+
export function resolveInnerInputType(inputTypeName, typeRegistry) {
|
|
218
234
|
const inputType = typeRegistry.get(inputTypeName);
|
|
219
235
|
if (!inputType?.inputFields)
|
|
220
236
|
return null;
|
|
@@ -232,7 +248,7 @@ function resolveInnerInputType(inputTypeName, typeRegistry) {
|
|
|
232
248
|
}
|
|
233
249
|
return null;
|
|
234
250
|
}
|
|
235
|
-
function getFieldsWithDefaults(table, typeRegistry) {
|
|
251
|
+
export function getFieldsWithDefaults(table, typeRegistry) {
|
|
236
252
|
const fieldsWithDefaults = new Set();
|
|
237
253
|
if (!typeRegistry)
|
|
238
254
|
return fieldsWithDefaults;
|
|
@@ -305,12 +321,20 @@ function buildMutationHandler(table, operation, targetName, typeRegistry, ormTyp
|
|
|
305
321
|
// For create: field is required only if it has no default value
|
|
306
322
|
// For update: all fields are optional (user only updates what they want)
|
|
307
323
|
const isRequired = operation === 'create' && !fieldsWithDefaults.has(field.name);
|
|
308
|
-
|
|
309
|
-
|
|
324
|
+
const hasDefault = fieldsWithDefaults.has(field.name);
|
|
325
|
+
const questionType = getQuestionTypeForField(field);
|
|
326
|
+
const questionProps = [
|
|
327
|
+
t.objectProperty(t.identifier('type'), t.stringLiteral(questionType)),
|
|
310
328
|
t.objectProperty(t.identifier('name'), t.stringLiteral(field.name)),
|
|
311
329
|
t.objectProperty(t.identifier('message'), t.stringLiteral(field.name)),
|
|
312
330
|
t.objectProperty(t.identifier('required'), t.booleanLiteral(isRequired)),
|
|
313
|
-
]
|
|
331
|
+
];
|
|
332
|
+
// Skip prompting for fields with backend-managed defaults.
|
|
333
|
+
// The field still appears in man pages and can be overridden via CLI flags.
|
|
334
|
+
if (hasDefault) {
|
|
335
|
+
questionProps.push(t.objectProperty(t.identifier('skipPrompt'), t.booleanLiteral(true)));
|
|
336
|
+
}
|
|
337
|
+
questions.push(t.objectExpression(questionProps));
|
|
314
338
|
}
|
|
315
339
|
}
|
|
316
340
|
const selectObj = operation === 'delete'
|
|
@@ -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;
|
|
@@ -67,6 +67,108 @@ export function getEditableFields(table) {
|
|
|
67
67
|
f.name !== 'createdAt' &&
|
|
68
68
|
f.name !== 'updatedAt');
|
|
69
69
|
}
|
|
70
|
+
function unwrapNonNull(typeRef) {
|
|
71
|
+
if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
|
|
72
|
+
return { inner: typeRef.ofType, required: true };
|
|
73
|
+
}
|
|
74
|
+
return { inner: typeRef, required: false };
|
|
75
|
+
}
|
|
76
|
+
function resolveBaseType(typeRef) {
|
|
77
|
+
if ((typeRef.kind === 'NON_NULL' || typeRef.kind === 'LIST') && typeRef.ofType) {
|
|
78
|
+
return resolveBaseType(typeRef.ofType);
|
|
79
|
+
}
|
|
80
|
+
return typeRef;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Strip internal type prefixes for cleaner docs display.
|
|
84
|
+
* e.g. 'ConstructiveInternalTypeEmail' -> 'Email'
|
|
85
|
+
*/
|
|
86
|
+
export function cleanTypeName(name) {
|
|
87
|
+
if (name.startsWith('ConstructiveInternalType')) {
|
|
88
|
+
return name.slice('ConstructiveInternalType'.length);
|
|
89
|
+
}
|
|
90
|
+
return name;
|
|
91
|
+
}
|
|
92
|
+
function getScalarTypeName(typeRef) {
|
|
93
|
+
const base = resolveBaseType(typeRef);
|
|
94
|
+
return cleanTypeName(base.name ?? 'String');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Flatten operation args for docs/skills, expanding INPUT_OBJECT types
|
|
98
|
+
* to dot-notation fields (e.g. input.email, input.password).
|
|
99
|
+
* Mirrors the logic in arg-mapper.ts buildQuestionsArray.
|
|
100
|
+
*/
|
|
101
|
+
/**
|
|
102
|
+
* Resolve inputFields for an INPUT_OBJECT type.
|
|
103
|
+
* Checks the CleanTypeRef first, then falls back to the TypeRegistry.
|
|
104
|
+
*/
|
|
105
|
+
function resolveInputFields(typeRef, registry) {
|
|
106
|
+
if (typeRef.inputFields && typeRef.inputFields.length > 0) {
|
|
107
|
+
return typeRef.inputFields;
|
|
108
|
+
}
|
|
109
|
+
if (registry && typeRef.name) {
|
|
110
|
+
const resolved = registry.get(typeRef.name);
|
|
111
|
+
if (resolved?.kind === 'INPUT_OBJECT' && resolved.inputFields && resolved.inputFields.length > 0) {
|
|
112
|
+
return resolved.inputFields;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
export function flattenArgs(args, registry) {
|
|
118
|
+
const result = [];
|
|
119
|
+
for (const arg of args) {
|
|
120
|
+
const { inner, required } = unwrapNonNull(arg.type);
|
|
121
|
+
const base = resolveBaseType(arg.type);
|
|
122
|
+
// Try to resolve inputFields from the inner type first (unwrapped NON_NULL)
|
|
123
|
+
const innerFields = inner.kind === 'INPUT_OBJECT'
|
|
124
|
+
? resolveInputFields(inner, registry)
|
|
125
|
+
: undefined;
|
|
126
|
+
if (innerFields) {
|
|
127
|
+
for (const field of innerFields) {
|
|
128
|
+
const { required: fieldRequired } = unwrapNonNull(field.type);
|
|
129
|
+
result.push({
|
|
130
|
+
flag: `${arg.name}.${field.name}`,
|
|
131
|
+
type: getScalarTypeName(field.type),
|
|
132
|
+
required: required && fieldRequired,
|
|
133
|
+
description: field.description,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Try the base type (unwrapped LIST+NON_NULL)
|
|
139
|
+
const baseFields = base.kind === 'INPUT_OBJECT'
|
|
140
|
+
? resolveInputFields(base, registry)
|
|
141
|
+
: undefined;
|
|
142
|
+
if (baseFields) {
|
|
143
|
+
for (const field of baseFields) {
|
|
144
|
+
const { required: fieldRequired } = unwrapNonNull(field.type);
|
|
145
|
+
result.push({
|
|
146
|
+
flag: `${arg.name}.${field.name}`,
|
|
147
|
+
type: getScalarTypeName(field.type),
|
|
148
|
+
required: required && fieldRequired,
|
|
149
|
+
description: field.description,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
result.push({
|
|
155
|
+
flag: arg.name,
|
|
156
|
+
type: getScalarTypeName(arg.type),
|
|
157
|
+
required,
|
|
158
|
+
description: arg.description,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build CLI flags string from flattened args.
|
|
167
|
+
* e.g. '--input.email <value> --input.password <value>'
|
|
168
|
+
*/
|
|
169
|
+
export function flattenedArgsToFlags(flatArgs) {
|
|
170
|
+
return flatArgs.map((a) => `--${a.flag} <value>`).join(' ');
|
|
171
|
+
}
|
|
70
172
|
export function gqlTypeToJsonSchemaType(gqlType) {
|
|
71
173
|
switch (gqlType) {
|
|
72
174
|
case 'Int':
|
|
@@ -214,6 +214,11 @@ const SCALAR_FILTER_CONFIGS = [
|
|
|
214
214
|
operators: ['equality', 'distinct', 'inArray', 'comparison', 'inet'],
|
|
215
215
|
},
|
|
216
216
|
{ name: 'FullTextFilter', tsType: 'string', operators: ['fulltext'] },
|
|
217
|
+
// VectorFilter: equality/distinct operators for vector columns on Filter types.
|
|
218
|
+
// Similarity search uses condition types (embeddingNearby), not filters, but
|
|
219
|
+
// connection-filter may still generate a filter for vector columns. This ensures
|
|
220
|
+
// the generated type uses number[] rather than being silently omitted.
|
|
221
|
+
{ name: 'VectorFilter', tsType: 'number[]', operators: ['equality', 'distinct'] },
|
|
217
222
|
// List filters (for array fields like string[], int[], uuid[])
|
|
218
223
|
{
|
|
219
224
|
name: 'StringListFilter',
|
|
@@ -710,10 +715,15 @@ function generateTableFilterTypes(tables) {
|
|
|
710
715
|
// ============================================================================
|
|
711
716
|
/**
|
|
712
717
|
* Build properties for a table condition interface
|
|
713
|
-
* Condition types are simpler than Filter types - they use direct value equality
|
|
718
|
+
* Condition types are simpler than Filter types - they use direct value equality.
|
|
719
|
+
*
|
|
720
|
+
* Also merges any extra fields from the GraphQL schema's condition type
|
|
721
|
+
* (e.g., plugin-injected fields like vectorEmbedding from VectorSearchPlugin)
|
|
722
|
+
* that are not derived from the table's own columns.
|
|
714
723
|
*/
|
|
715
|
-
function buildTableConditionProperties(table) {
|
|
724
|
+
function buildTableConditionProperties(table, typeRegistry) {
|
|
716
725
|
const properties = [];
|
|
726
|
+
const generatedFieldNames = new Set();
|
|
717
727
|
for (const field of table.fields) {
|
|
718
728
|
const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
|
|
719
729
|
if (isRelationField(field.name, table))
|
|
@@ -725,17 +735,38 @@ function buildTableConditionProperties(table) {
|
|
|
725
735
|
type: `${tsType} | null`,
|
|
726
736
|
optional: true,
|
|
727
737
|
});
|
|
738
|
+
generatedFieldNames.add(field.name);
|
|
739
|
+
}
|
|
740
|
+
// Merge any additional fields from the schema's condition type
|
|
741
|
+
// (e.g., plugin-added fields like vectorEmbedding from VectorSearchPlugin)
|
|
742
|
+
if (typeRegistry) {
|
|
743
|
+
const conditionTypeName = getConditionTypeName(table);
|
|
744
|
+
const conditionType = typeRegistry.get(conditionTypeName);
|
|
745
|
+
if (conditionType?.kind === 'INPUT_OBJECT' &&
|
|
746
|
+
conditionType.inputFields) {
|
|
747
|
+
for (const field of conditionType.inputFields) {
|
|
748
|
+
if (generatedFieldNames.has(field.name))
|
|
749
|
+
continue;
|
|
750
|
+
const tsType = typeRefToTs(field.type);
|
|
751
|
+
properties.push({
|
|
752
|
+
name: field.name,
|
|
753
|
+
type: tsType,
|
|
754
|
+
optional: true,
|
|
755
|
+
description: stripSmartComments(field.description, true),
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
728
759
|
}
|
|
729
760
|
return properties;
|
|
730
761
|
}
|
|
731
762
|
/**
|
|
732
763
|
* Generate table condition type statements
|
|
733
764
|
*/
|
|
734
|
-
function generateTableConditionTypes(tables) {
|
|
765
|
+
function generateTableConditionTypes(tables, typeRegistry) {
|
|
735
766
|
const statements = [];
|
|
736
767
|
for (const table of tables) {
|
|
737
768
|
const conditionName = getConditionTypeName(table);
|
|
738
|
-
statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table)));
|
|
769
|
+
statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table, typeRegistry)));
|
|
739
770
|
}
|
|
740
771
|
if (statements.length > 0) {
|
|
741
772
|
addSectionComment(statements, 'Table Condition Types');
|
|
@@ -746,9 +777,13 @@ function generateTableConditionTypes(tables) {
|
|
|
746
777
|
// OrderBy Types Generator (AST-based)
|
|
747
778
|
// ============================================================================
|
|
748
779
|
/**
|
|
749
|
-
* Build OrderBy union type values
|
|
780
|
+
* Build OrderBy union type values.
|
|
781
|
+
*
|
|
782
|
+
* Also merges any extra values from the GraphQL schema's orderBy enum
|
|
783
|
+
* (e.g., plugin-injected values like EMBEDDING_DISTANCE_ASC/DESC
|
|
784
|
+
* from VectorSearchPlugin).
|
|
750
785
|
*/
|
|
751
|
-
function buildOrderByValues(table) {
|
|
786
|
+
function buildOrderByValues(table, typeRegistry) {
|
|
752
787
|
const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
|
|
753
788
|
for (const field of table.fields) {
|
|
754
789
|
if (isRelationField(field.name, table))
|
|
@@ -757,16 +792,30 @@ function buildOrderByValues(table) {
|
|
|
757
792
|
values.push(`${upperSnake}_ASC`);
|
|
758
793
|
values.push(`${upperSnake}_DESC`);
|
|
759
794
|
}
|
|
795
|
+
// Merge any additional values from the schema's orderBy enum type
|
|
796
|
+
// (e.g., plugin-added values like EMBEDDING_DISTANCE_ASC/DESC)
|
|
797
|
+
if (typeRegistry) {
|
|
798
|
+
const orderByTypeName = getOrderByTypeName(table);
|
|
799
|
+
const orderByType = typeRegistry.get(orderByTypeName);
|
|
800
|
+
if (orderByType?.kind === 'ENUM' && orderByType.enumValues) {
|
|
801
|
+
const existingValues = new Set(values);
|
|
802
|
+
for (const value of orderByType.enumValues) {
|
|
803
|
+
if (!existingValues.has(value)) {
|
|
804
|
+
values.push(value);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
760
809
|
return values;
|
|
761
810
|
}
|
|
762
811
|
/**
|
|
763
812
|
* Generate OrderBy type statements
|
|
764
813
|
*/
|
|
765
|
-
function generateOrderByTypes(tables) {
|
|
814
|
+
function generateOrderByTypes(tables, typeRegistry) {
|
|
766
815
|
const statements = [];
|
|
767
816
|
for (const table of tables) {
|
|
768
817
|
const enumName = getOrderByTypeName(table);
|
|
769
|
-
const values = buildOrderByValues(table);
|
|
818
|
+
const values = buildOrderByValues(table, typeRegistry);
|
|
770
819
|
const unionType = createStringLiteralUnion(values);
|
|
771
820
|
const typeAlias = t.tsTypeAliasDeclaration(t.identifier(enumName), null, unionType);
|
|
772
821
|
statements.push(t.exportNamedDeclaration(typeAlias));
|
|
@@ -1017,11 +1066,12 @@ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes,
|
|
|
1017
1066
|
optional,
|
|
1018
1067
|
description: stripSmartComments(field.description, comments),
|
|
1019
1068
|
});
|
|
1020
|
-
// Follow nested Input
|
|
1069
|
+
// Follow nested types (Input objects, Enums, etc.) that exist in the registry
|
|
1021
1070
|
const baseType = getTypeBaseName(field.type);
|
|
1022
1071
|
if (baseType &&
|
|
1023
|
-
|
|
1024
|
-
!generatedTypes.has(baseType)
|
|
1072
|
+
!SCALAR_NAMES.has(baseType) &&
|
|
1073
|
+
!generatedTypes.has(baseType) &&
|
|
1074
|
+
typeRegistry.has(baseType)) {
|
|
1025
1075
|
typesToGenerate.add(baseType);
|
|
1026
1076
|
}
|
|
1027
1077
|
}
|
|
@@ -1205,6 +1255,42 @@ function generateConnectionFieldsMap(tables, tableByName) {
|
|
|
1205
1255
|
return statements;
|
|
1206
1256
|
}
|
|
1207
1257
|
// ============================================================================
|
|
1258
|
+
// Plugin-Injected Type Collector
|
|
1259
|
+
// ============================================================================
|
|
1260
|
+
/**
|
|
1261
|
+
* Collect extra input type names referenced by plugin-injected condition fields.
|
|
1262
|
+
*
|
|
1263
|
+
* When plugins (like VectorSearchPlugin) inject fields into condition types,
|
|
1264
|
+
* they reference types (like VectorNearbyInput, VectorMetric) that also need
|
|
1265
|
+
* to be generated. This function discovers those types by comparing the
|
|
1266
|
+
* schema's condition type fields against the table's own columns.
|
|
1267
|
+
*/
|
|
1268
|
+
function collectConditionExtraInputTypes(tables, typeRegistry) {
|
|
1269
|
+
const extraTypes = new Set();
|
|
1270
|
+
for (const table of tables) {
|
|
1271
|
+
const conditionTypeName = getConditionTypeName(table);
|
|
1272
|
+
const conditionType = typeRegistry.get(conditionTypeName);
|
|
1273
|
+
if (!conditionType ||
|
|
1274
|
+
conditionType.kind !== 'INPUT_OBJECT' ||
|
|
1275
|
+
!conditionType.inputFields) {
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
const tableFieldNames = new Set(table.fields
|
|
1279
|
+
.filter((f) => !isRelationField(f.name, table))
|
|
1280
|
+
.map((f) => f.name));
|
|
1281
|
+
for (const field of conditionType.inputFields) {
|
|
1282
|
+
if (tableFieldNames.has(field.name))
|
|
1283
|
+
continue;
|
|
1284
|
+
// Collect the base type name of this extra field
|
|
1285
|
+
const baseName = getTypeBaseName(field.type);
|
|
1286
|
+
if (baseName && !SCALAR_NAMES.has(baseName)) {
|
|
1287
|
+
extraTypes.add(baseName);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return extraTypes;
|
|
1292
|
+
}
|
|
1293
|
+
// ============================================================================
|
|
1208
1294
|
// Main Generator (AST-based)
|
|
1209
1295
|
// ============================================================================
|
|
1210
1296
|
/**
|
|
@@ -1235,9 +1321,13 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
|
|
|
1235
1321
|
// 4. Table filter types
|
|
1236
1322
|
statements.push(...generateTableFilterTypes(tablesList));
|
|
1237
1323
|
// 4b. Table condition types (simple equality filter)
|
|
1238
|
-
|
|
1324
|
+
// Pass typeRegistry to merge plugin-injected condition fields
|
|
1325
|
+
// (e.g., vectorEmbedding from VectorSearchPlugin)
|
|
1326
|
+
statements.push(...generateTableConditionTypes(tablesList, typeRegistry));
|
|
1239
1327
|
// 5. OrderBy types
|
|
1240
|
-
|
|
1328
|
+
// Pass typeRegistry to merge plugin-injected orderBy values
|
|
1329
|
+
// (e.g., EMBEDDING_DISTANCE_ASC/DESC from VectorSearchPlugin)
|
|
1330
|
+
statements.push(...generateOrderByTypes(tablesList, typeRegistry));
|
|
1241
1331
|
// 6. CRUD input types
|
|
1242
1332
|
statements.push(...generateAllCrudInputTypes(tablesList, typeRegistry));
|
|
1243
1333
|
}
|
|
@@ -1245,8 +1335,16 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
|
|
|
1245
1335
|
// Always emit this export so generated model/custom-op imports stay valid.
|
|
1246
1336
|
statements.push(...generateConnectionFieldsMap(tablesList, tableByName));
|
|
1247
1337
|
// 7. Custom input types from TypeRegistry
|
|
1338
|
+
// Also include any extra types referenced by plugin-injected condition fields
|
|
1339
|
+
const mergedUsedInputTypes = new Set(usedInputTypes);
|
|
1340
|
+
if (hasTables) {
|
|
1341
|
+
const conditionExtraTypes = collectConditionExtraInputTypes(tablesList, typeRegistry);
|
|
1342
|
+
for (const typeName of conditionExtraTypes) {
|
|
1343
|
+
mergedUsedInputTypes.add(typeName);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1248
1346
|
const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
|
|
1249
|
-
statements.push(...generateCustomInputTypes(typeRegistry,
|
|
1347
|
+
statements.push(...generateCustomInputTypes(typeRegistry, mergedUsedInputTypes, tableCrudTypes, comments));
|
|
1250
1348
|
// 8. Payload/return types for custom operations
|
|
1251
1349
|
if (usedPayloadTypes && usedPayloadTypes.size > 0) {
|
|
1252
1350
|
const alreadyGeneratedTypes = new Set();
|
|
@@ -74,6 +74,7 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
74
74
|
const selectTypeName = `${typeName}Select`;
|
|
75
75
|
const relationTypeName = `${typeName}WithRelations`;
|
|
76
76
|
const whereTypeName = getFilterTypeName(table);
|
|
77
|
+
const conditionTypeName = `${typeName}Condition`;
|
|
77
78
|
const orderByTypeName = getOrderByTypeName(table);
|
|
78
79
|
const createInputTypeName = `Create${typeName}Input`;
|
|
79
80
|
const updateInputTypeName = `Update${typeName}Input`;
|
|
@@ -113,6 +114,7 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
113
114
|
relationTypeName,
|
|
114
115
|
selectTypeName,
|
|
115
116
|
whereTypeName,
|
|
117
|
+
conditionTypeName,
|
|
116
118
|
orderByTypeName,
|
|
117
119
|
createInputTypeName,
|
|
118
120
|
updateInputTypeName,
|
|
@@ -134,6 +136,7 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
134
136
|
const argsType = (sel) => t.tsTypeReference(t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation([
|
|
135
137
|
sel,
|
|
136
138
|
t.tsTypeReference(t.identifier(whereTypeName)),
|
|
139
|
+
t.tsTypeReference(t.identifier(conditionTypeName)),
|
|
137
140
|
t.tsTypeReference(t.identifier(orderByTypeName)),
|
|
138
141
|
]));
|
|
139
142
|
const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
|
|
@@ -159,6 +162,7 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
159
162
|
selectExpr,
|
|
160
163
|
t.objectExpression([
|
|
161
164
|
t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
|
|
165
|
+
t.objectProperty(t.identifier('condition'), t.optionalMemberExpression(t.identifier('args'), t.identifier('condition'), false, true)),
|
|
162
166
|
t.objectProperty(t.identifier('orderBy'), t.tsAsExpression(t.optionalMemberExpression(t.identifier('args'), t.identifier('orderBy'), false, true), t.tsUnionType([
|
|
163
167
|
t.tsArrayType(t.tsStringKeyword()),
|
|
164
168
|
t.tsUndefinedKeyword(),
|
|
@@ -172,6 +176,7 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
172
176
|
t.stringLiteral(whereTypeName),
|
|
173
177
|
t.stringLiteral(orderByTypeName),
|
|
174
178
|
t.identifier('connectionFieldsMap'),
|
|
179
|
+
t.stringLiteral(conditionTypeName),
|
|
175
180
|
];
|
|
176
181
|
classBody.push(createClassMethod('findMany', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindManyDocument', bodyArgs, 'query', typeName, pluralQueryName)));
|
|
177
182
|
}
|
|
@@ -180,6 +185,7 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
180
185
|
const argsType = (sel) => t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
|
|
181
186
|
sel,
|
|
182
187
|
t.tsTypeReference(t.identifier(whereTypeName)),
|
|
188
|
+
t.tsTypeReference(t.identifier(conditionTypeName)),
|
|
183
189
|
]));
|
|
184
190
|
const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
|
|
185
191
|
t.tsTypeLiteral([
|
|
@@ -204,9 +210,11 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
204
210
|
selectExpr,
|
|
205
211
|
t.objectExpression([
|
|
206
212
|
t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
|
|
213
|
+
t.objectProperty(t.identifier('condition'), t.optionalMemberExpression(t.identifier('args'), t.identifier('condition'), false, true)),
|
|
207
214
|
]),
|
|
208
215
|
t.stringLiteral(whereTypeName),
|
|
209
216
|
t.identifier('connectionFieldsMap'),
|
|
217
|
+
t.stringLiteral(conditionTypeName),
|
|
210
218
|
];
|
|
211
219
|
classBody.push(createClassMethod('findFirst', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindFirstDocument', bodyArgs, 'query', typeName, pluralQueryName)));
|
|
212
220
|
}
|
|
@@ -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
|
|
@@ -33,6 +33,8 @@ export const SCALAR_TS_MAP = {
|
|
|
33
33
|
MacAddr: 'string',
|
|
34
34
|
TsVector: 'string',
|
|
35
35
|
TsQuery: 'string',
|
|
36
|
+
// Vector types (pgvector) — serialized as [Float] in GraphQL
|
|
37
|
+
Vector: 'number[]',
|
|
36
38
|
// File upload
|
|
37
39
|
Upload: 'File',
|
|
38
40
|
};
|
|
@@ -51,6 +53,12 @@ export const SCALAR_FILTER_MAP = {
|
|
|
51
53
|
BigFloat: 'BigFloatFilter',
|
|
52
54
|
BitString: 'BitStringFilter',
|
|
53
55
|
InternetAddress: 'InternetAddressFilter',
|
|
56
|
+
// VectorFilter provides equality/distinct operators (isNull, equalTo, etc.) for vector
|
|
57
|
+
// columns on Filter types. While similarity search is done via condition types
|
|
58
|
+
// (e.g., embeddingNearby on ContactCondition), postgraphile-plugin-connection-filter
|
|
59
|
+
// may still auto-generate a filter type for vector columns. Without this mapping,
|
|
60
|
+
// those fields would be silently omitted from the generated SDK.
|
|
61
|
+
Vector: 'VectorFilter',
|
|
54
62
|
FullText: 'FullTextFilter',
|
|
55
63
|
Interval: 'StringFilter',
|
|
56
64
|
};
|
package/esm/core/generate.js
CHANGED
|
@@ -274,18 +274,18 @@ export async function generate(options = {}, internalOptions) {
|
|
|
274
274
|
? config.cli.toolName
|
|
275
275
|
: 'app';
|
|
276
276
|
if (docsConfig.readme) {
|
|
277
|
-
const readme = generateCliReadme(tables, allCustomOps, toolName);
|
|
277
|
+
const readme = generateCliReadme(tables, allCustomOps, toolName, customOperations.typeRegistry);
|
|
278
278
|
filesToWrite.push({ path: path.posix.join('cli', readme.fileName), content: readme.content });
|
|
279
279
|
}
|
|
280
280
|
if (docsConfig.agents) {
|
|
281
|
-
const agents = generateCliAgentsDocs(tables, allCustomOps, toolName);
|
|
281
|
+
const agents = generateCliAgentsDocs(tables, allCustomOps, toolName, customOperations.typeRegistry);
|
|
282
282
|
filesToWrite.push({ path: path.posix.join('cli', agents.fileName), content: agents.content });
|
|
283
283
|
}
|
|
284
284
|
if (docsConfig.mcp) {
|
|
285
|
-
allMcpTools.push(...getCliMcpTools(tables, allCustomOps, toolName));
|
|
285
|
+
allMcpTools.push(...getCliMcpTools(tables, allCustomOps, toolName, customOperations.typeRegistry));
|
|
286
286
|
}
|
|
287
287
|
if (docsConfig.skills) {
|
|
288
|
-
for (const skill of generateCliSkills(tables, allCustomOps, toolName, targetName)) {
|
|
288
|
+
for (const skill of generateCliSkills(tables, allCustomOps, toolName, targetName, customOperations.typeRegistry)) {
|
|
289
289
|
skillsToWrite.push({ path: skill.fileName, content: skill.content });
|
|
290
290
|
}
|
|
291
291
|
}
|
|
@@ -574,9 +574,19 @@ export async function generateMulti(options) {
|
|
|
574
574
|
const docsConfig = resolveDocsConfig(firstTargetDocsConfig);
|
|
575
575
|
const { resolveBuiltinNames } = await import('./codegen/cli');
|
|
576
576
|
const builtinNames = resolveBuiltinNames(cliTargets.map((t) => t.name), cliConfig.builtinNames);
|
|
577
|
+
// Merge all target type registries into a combined registry for docs generation
|
|
578
|
+
const combinedRegistry = new Map();
|
|
579
|
+
for (const t of cliTargets) {
|
|
580
|
+
if (t.typeRegistry) {
|
|
581
|
+
for (const [key, value] of t.typeRegistry) {
|
|
582
|
+
combinedRegistry.set(key, value);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
577
586
|
const docsInput = {
|
|
578
587
|
toolName,
|
|
579
588
|
builtinNames,
|
|
589
|
+
registry: combinedRegistry.size > 0 ? combinedRegistry : undefined,
|
|
580
590
|
targets: cliTargets.map((t) => ({
|
|
581
591
|
name: t.name,
|
|
582
592
|
endpoint: t.endpoint,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-codegen",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.2",
|
|
4
4
|
"description": "GraphQL SDK generator for Constructive databases with React Query hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -56,25 +56,25 @@
|
|
|
56
56
|
"@0no-co/graphql.web": "^1.1.2",
|
|
57
57
|
"@babel/generator": "^7.29.1",
|
|
58
58
|
"@babel/types": "^7.29.0",
|
|
59
|
-
"@constructive-io/graphql-query": "^3.
|
|
60
|
-
"@constructive-io/graphql-types": "^3.
|
|
59
|
+
"@constructive-io/graphql-query": "^3.5.2",
|
|
60
|
+
"@constructive-io/graphql-types": "^3.3.2",
|
|
61
61
|
"@inquirerer/utils": "^3.3.1",
|
|
62
|
-
"@pgpmjs/core": "^6.
|
|
62
|
+
"@pgpmjs/core": "^6.6.2",
|
|
63
63
|
"ajv": "^8.18.0",
|
|
64
64
|
"deepmerge": "^4.3.1",
|
|
65
65
|
"find-and-require-package-json": "^0.9.1",
|
|
66
|
-
"gql-ast": "^3.
|
|
67
|
-
"graphile-schema": "^1.
|
|
66
|
+
"gql-ast": "^3.3.2",
|
|
67
|
+
"graphile-schema": "^1.5.2",
|
|
68
68
|
"graphql": "^16.13.0",
|
|
69
69
|
"inflekt": "^0.3.3",
|
|
70
70
|
"inquirerer": "^4.5.2",
|
|
71
71
|
"jiti": "^2.6.1",
|
|
72
72
|
"komoji": "^0.8.1",
|
|
73
73
|
"oxfmt": "^0.36.0",
|
|
74
|
-
"pg-cache": "^3.
|
|
75
|
-
"pg-env": "^1.
|
|
76
|
-
"pgsql-client": "^3.
|
|
77
|
-
"pgsql-seed": "^2.
|
|
74
|
+
"pg-cache": "^3.3.2",
|
|
75
|
+
"pg-env": "^1.7.2",
|
|
76
|
+
"pgsql-client": "^3.5.2",
|
|
77
|
+
"pgsql-seed": "^2.5.2",
|
|
78
78
|
"undici": "^7.22.0"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
@@ -101,5 +101,5 @@
|
|
|
101
101
|
"tsx": "^4.21.0",
|
|
102
102
|
"typescript": "^5.9.3"
|
|
103
103
|
},
|
|
104
|
-
"gitHead": "
|
|
104
|
+
"gitHead": "4fd2c9be786ad9ae2213453276a69723435d5315"
|
|
105
105
|
}
|