@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
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveInnerInputType = resolveInnerInputType;
37
+ exports.getFieldsWithDefaults = getFieldsWithDefaults;
36
38
  exports.generateTableCommand = generateTableCommand;
37
39
  const t = __importStar(require("@babel/types"));
38
40
  const komoji_1 = require("komoji");
@@ -103,6 +105,22 @@ function getTsTypeForField(field) {
103
105
  return t.tsStringKeyword();
104
106
  }
105
107
  }
108
+ /**
109
+ * Maps a GraphQL scalar type to the appropriate inquirerer question type.
110
+ * Used by table CRUD commands to generate semantic prompts.
111
+ */
112
+ function getQuestionTypeForField(field) {
113
+ const gqlType = field.type.gqlType.replace(/!/g, '');
114
+ switch (gqlType) {
115
+ case 'Boolean':
116
+ return 'boolean';
117
+ case 'JSON':
118
+ case 'GeoJSON':
119
+ return 'json';
120
+ default:
121
+ return 'text';
122
+ }
123
+ }
106
124
  function buildFieldSchemaObject(table) {
107
125
  const fields = (0, utils_1.getScalarFields)(table);
108
126
  return t.objectExpression(fields.map((f) => {
@@ -341,12 +359,20 @@ function buildMutationHandler(table, operation, targetName, typeRegistry, ormTyp
341
359
  // For create: field is required only if it has no default value
342
360
  // For update: all fields are optional (user only updates what they want)
343
361
  const isRequired = operation === 'create' && !fieldsWithDefaults.has(field.name);
344
- questions.push(t.objectExpression([
345
- t.objectProperty(t.identifier('type'), t.stringLiteral('text')),
362
+ const hasDefault = fieldsWithDefaults.has(field.name);
363
+ const questionType = getQuestionTypeForField(field);
364
+ const questionProps = [
365
+ t.objectProperty(t.identifier('type'), t.stringLiteral(questionType)),
346
366
  t.objectProperty(t.identifier('name'), t.stringLiteral(field.name)),
347
367
  t.objectProperty(t.identifier('message'), t.stringLiteral(field.name)),
348
368
  t.objectProperty(t.identifier('required'), t.booleanLiteral(isRequired)),
349
- ]));
369
+ ];
370
+ // Skip prompting for fields with backend-managed defaults.
371
+ // The field still appears in man pages and can be overridden via CLI flags.
372
+ if (hasDefault) {
373
+ questionProps.push(t.objectProperty(t.identifier('skipPrompt'), t.booleanLiteral(true)));
374
+ }
375
+ questions.push(t.objectExpression(questionProps));
350
376
  }
351
377
  }
352
378
  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;
@@ -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 types
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
- baseType.endsWith('Input') &&
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
- statements.push(...generateTableConditionTypes(tablesList));
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
- statements.push(...generateOrderByTypes(tablesList));
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, usedInputTypes, tableCrudTypes, comments));
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
@@ -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: string;
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({