@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.
Files changed (33) hide show
  1. package/core/codegen/cli/arg-mapper.d.ts +1 -1
  2. package/core/codegen/cli/arg-mapper.js +15 -11
  3. package/core/codegen/cli/custom-command-generator.js +4 -3
  4. package/core/codegen/cli/docs-generator.d.ts +6 -5
  5. package/core/codegen/cli/docs-generator.js +167 -64
  6. package/core/codegen/cli/table-command-generator.d.ts +15 -0
  7. package/core/codegen/cli/table-command-generator.js +29 -3
  8. package/core/codegen/docs-utils.d.ts +26 -1
  9. package/core/codegen/docs-utils.js +105 -0
  10. package/core/codegen/orm/input-types-generator.js +112 -14
  11. package/core/codegen/orm/model-generator.js +8 -0
  12. package/core/codegen/orm/select-types.d.ts +4 -2
  13. package/core/codegen/scalars.js +8 -0
  14. package/core/codegen/templates/cli-entry.ts +2 -2
  15. package/core/codegen/templates/cli-utils.ts +28 -0
  16. package/core/codegen/templates/query-builder.ts +28 -5
  17. package/core/codegen/templates/select-types.ts +4 -2
  18. package/core/generate.js +14 -4
  19. package/esm/core/codegen/cli/arg-mapper.d.ts +1 -1
  20. package/esm/core/codegen/cli/arg-mapper.js +15 -11
  21. package/esm/core/codegen/cli/custom-command-generator.js +4 -3
  22. package/esm/core/codegen/cli/docs-generator.d.ts +6 -5
  23. package/esm/core/codegen/cli/docs-generator.js +168 -65
  24. package/esm/core/codegen/cli/table-command-generator.d.ts +15 -0
  25. package/esm/core/codegen/cli/table-command-generator.js +29 -5
  26. package/esm/core/codegen/docs-utils.d.ts +26 -1
  27. package/esm/core/codegen/docs-utils.js +102 -0
  28. package/esm/core/codegen/orm/input-types-generator.js +112 -14
  29. package/esm/core/codegen/orm/model-generator.js +8 -0
  30. package/esm/core/codegen/orm/select-types.d.ts +4 -2
  31. package/esm/core/codegen/scalars.js +8 -0
  32. package/esm/core/generate.js +14 -4
  33. 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
- questions.push(t.objectExpression([
309
- t.objectProperty(t.identifier('type'), t.stringLiteral('text')),
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 types
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
- baseType.endsWith('Input') &&
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
- statements.push(...generateTableConditionTypes(tablesList));
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
- statements.push(...generateOrderByTypes(tablesList));
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, usedInputTypes, tableCrudTypes, comments));
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
  };
@@ -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.8.1",
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.3.4",
60
- "@constructive-io/graphql-types": "^3.1.2",
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.4.2",
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.1.1",
67
- "graphile-schema": "^1.3.4",
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.1.1",
75
- "pg-env": "^1.5.0",
76
- "pgsql-client": "^3.3.2",
77
- "pgsql-seed": "^2.3.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": "f4176b73429bffca0aec8dc89abe9835bf40e5c6"
104
+ "gitHead": "4fd2c9be786ad9ae2213453276a69723435d5315"
105
105
  }