@constructive-io/graphql-codegen 4.9.0 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) 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/command-map-generator.d.ts +1 -0
  4. package/core/codegen/cli/command-map-generator.js +4 -0
  5. package/core/codegen/cli/config-command-generator.d.ts +11 -0
  6. package/core/codegen/cli/config-command-generator.js +458 -0
  7. package/core/codegen/cli/custom-command-generator.js +4 -3
  8. package/core/codegen/cli/docs-generator.d.ts +6 -5
  9. package/core/codegen/cli/docs-generator.js +167 -64
  10. package/core/codegen/cli/helpers-generator.d.ts +15 -0
  11. package/core/codegen/cli/helpers-generator.js +119 -0
  12. package/core/codegen/cli/index.d.ts +4 -0
  13. package/core/codegen/cli/index.js +21 -3
  14. package/core/codegen/cli/table-command-generator.d.ts +15 -0
  15. package/core/codegen/cli/table-command-generator.js +20 -1
  16. package/core/codegen/docs-utils.d.ts +26 -1
  17. package/core/codegen/docs-utils.js +105 -0
  18. package/core/codegen/orm/index.js +3 -2
  19. package/core/codegen/orm/input-types-generator.d.ts +3 -1
  20. package/core/codegen/orm/input-types-generator.js +123 -17
  21. package/core/codegen/orm/model-generator.d.ts +6 -2
  22. package/core/codegen/orm/model-generator.js +59 -29
  23. package/core/codegen/orm/select-types.d.ts +4 -2
  24. package/core/codegen/scalars.js +8 -0
  25. package/core/codegen/templates/cli-entry.ts +2 -2
  26. package/core/codegen/templates/cli-utils.ts +28 -0
  27. package/core/codegen/templates/query-builder.ts +28 -5
  28. package/core/codegen/templates/select-types.ts +4 -2
  29. package/core/generate.js +14 -4
  30. package/esm/core/codegen/cli/arg-mapper.d.ts +1 -1
  31. package/esm/core/codegen/cli/arg-mapper.js +15 -11
  32. package/esm/core/codegen/cli/command-map-generator.d.ts +1 -0
  33. package/esm/core/codegen/cli/command-map-generator.js +4 -0
  34. package/esm/core/codegen/cli/config-command-generator.d.ts +11 -0
  35. package/esm/core/codegen/cli/config-command-generator.js +422 -0
  36. package/esm/core/codegen/cli/custom-command-generator.js +4 -3
  37. package/esm/core/codegen/cli/docs-generator.d.ts +6 -5
  38. package/esm/core/codegen/cli/docs-generator.js +168 -65
  39. package/esm/core/codegen/cli/helpers-generator.d.ts +15 -0
  40. package/esm/core/codegen/cli/helpers-generator.js +83 -0
  41. package/esm/core/codegen/cli/index.d.ts +4 -0
  42. package/esm/core/codegen/cli/index.js +18 -2
  43. package/esm/core/codegen/cli/table-command-generator.d.ts +15 -0
  44. package/esm/core/codegen/cli/table-command-generator.js +20 -3
  45. package/esm/core/codegen/docs-utils.d.ts +26 -1
  46. package/esm/core/codegen/docs-utils.js +102 -0
  47. package/esm/core/codegen/orm/index.js +3 -2
  48. package/esm/core/codegen/orm/input-types-generator.d.ts +3 -1
  49. package/esm/core/codegen/orm/input-types-generator.js +123 -17
  50. package/esm/core/codegen/orm/model-generator.d.ts +6 -2
  51. package/esm/core/codegen/orm/model-generator.js +59 -29
  52. package/esm/core/codegen/orm/select-types.d.ts +4 -2
  53. package/esm/core/codegen/scalars.js +8 -0
  54. package/esm/core/generate.js +14 -4
  55. package/esm/types/config.d.ts +9 -0
  56. package/esm/types/config.js +1 -0
  57. package/package.json +11 -11
  58. package/types/config.d.ts +9 -0
  59. package/types/config.js +1 -0
@@ -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));
@@ -1013,9 +1062,9 @@ function buildTableCrudTypeNames(tables) {
1013
1062
  /**
1014
1063
  * Generate custom input type statements from TypeRegistry
1015
1064
  */
1016
- function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes, comments = true) {
1065
+ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes, comments = true, alreadyGeneratedTypes) {
1017
1066
  const statements = [];
1018
- const generatedTypes = new Set();
1067
+ const generatedTypes = new Set(alreadyGeneratedTypes ?? []);
1019
1068
  const typesToGenerate = new Set(Array.from(usedInputTypes));
1020
1069
  // Filter out types we've already generated (exact matches for table CRUD types only)
1021
1070
  if (tableCrudTypes) {
@@ -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,12 +1293,49 @@ 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
  /**
1249
1335
  * Generate comprehensive input-types.ts file using Babel AST
1250
1336
  */
1251
- function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes, comments = true) {
1337
+ function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes, comments = true, options) {
1338
+ const conditionEnabled = options?.condition !== false;
1252
1339
  const statements = [];
1253
1340
  const tablesList = tables ?? [];
1254
1341
  const hasTables = tablesList.length > 0;
@@ -1273,9 +1360,15 @@ function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloa
1273
1360
  // 4. Table filter types
1274
1361
  statements.push(...generateTableFilterTypes(tablesList));
1275
1362
  // 4b. Table condition types (simple equality filter)
1276
- statements.push(...generateTableConditionTypes(tablesList));
1363
+ // Pass typeRegistry to merge plugin-injected condition fields
1364
+ // (e.g., vectorEmbedding from VectorSearchPlugin)
1365
+ if (conditionEnabled) {
1366
+ statements.push(...generateTableConditionTypes(tablesList, typeRegistry));
1367
+ }
1277
1368
  // 5. OrderBy types
1278
- statements.push(...generateOrderByTypes(tablesList));
1369
+ // Pass typeRegistry to merge plugin-injected orderBy values
1370
+ // (e.g., EMBEDDING_DISTANCE_ASC/DESC from VectorSearchPlugin)
1371
+ statements.push(...generateOrderByTypes(tablesList, typeRegistry));
1279
1372
  // 6. CRUD input types
1280
1373
  statements.push(...generateAllCrudInputTypes(tablesList, typeRegistry));
1281
1374
  }
@@ -1283,8 +1376,21 @@ function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloa
1283
1376
  // Always emit this export so generated model/custom-op imports stay valid.
1284
1377
  statements.push(...generateConnectionFieldsMap(tablesList, tableByName));
1285
1378
  // 7. Custom input types from TypeRegistry
1379
+ // Also include any extra types referenced by plugin-injected condition fields
1380
+ const mergedUsedInputTypes = new Set(usedInputTypes);
1381
+ if (hasTables && conditionEnabled) {
1382
+ const conditionExtraTypes = collectConditionExtraInputTypes(tablesList, typeRegistry);
1383
+ for (const typeName of conditionExtraTypes) {
1384
+ mergedUsedInputTypes.add(typeName);
1385
+ }
1386
+ }
1286
1387
  const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
1287
- statements.push(...generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes, comments));
1388
+ // Pass customScalarTypes + enumTypes as already-generated to avoid duplicate declarations
1389
+ const alreadyGenerated = new Set([
1390
+ ...customScalarTypes,
1391
+ ...enumTypes,
1392
+ ]);
1393
+ statements.push(...generateCustomInputTypes(typeRegistry, mergedUsedInputTypes, tableCrudTypes, comments, alreadyGenerated));
1288
1394
  // 8. Payload/return types for custom operations
1289
1395
  if (usedPayloadTypes && usedPayloadTypes.size > 0) {
1290
1396
  const alreadyGeneratedTypes = new Set();
@@ -5,5 +5,9 @@ export interface GeneratedModelFile {
5
5
  modelName: string;
6
6
  tableName: string;
7
7
  }
8
- export declare function generateModelFile(table: CleanTable, _useSharedTypes: boolean): GeneratedModelFile;
9
- export declare function generateAllModelFiles(tables: CleanTable[], useSharedTypes: boolean): GeneratedModelFile[];
8
+ export declare function generateModelFile(table: CleanTable, _useSharedTypes: boolean, options?: {
9
+ condition?: boolean;
10
+ }): GeneratedModelFile;
11
+ export declare function generateAllModelFiles(tables: CleanTable[], useSharedTypes: boolean, options?: {
12
+ condition?: boolean;
13
+ }): GeneratedModelFile[];
@@ -102,7 +102,8 @@ function strictSelectGuard(selectTypeName) {
102
102
  t.tsTypeReference(t.identifier(selectTypeName)),
103
103
  ]));
104
104
  }
105
- function generateModelFile(table, _useSharedTypes) {
105
+ function generateModelFile(table, _useSharedTypes, options) {
106
+ const conditionEnabled = options?.condition !== false;
106
107
  const { typeName, singularName, pluralName } = (0, utils_1.getTableNames)(table);
107
108
  const modelName = `${typeName}Model`;
108
109
  const baseFileName = (0, utils_1.lcFirst)(typeName);
@@ -111,6 +112,7 @@ function generateModelFile(table, _useSharedTypes) {
111
112
  const selectTypeName = `${typeName}Select`;
112
113
  const relationTypeName = `${typeName}WithRelations`;
113
114
  const whereTypeName = (0, utils_1.getFilterTypeName)(table);
115
+ const conditionTypeName = conditionEnabled ? `${typeName}Condition` : undefined;
114
116
  const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
115
117
  const createInputTypeName = `Create${typeName}Input`;
116
118
  const updateInputTypeName = `Update${typeName}Input`;
@@ -145,16 +147,18 @@ function generateModelFile(table, _useSharedTypes) {
145
147
  'InferSelectResult',
146
148
  'StrictSelect',
147
149
  ], true));
148
- statements.push(createImportDeclaration('../input-types', [
150
+ const inputTypeImports = [
149
151
  typeName,
150
152
  relationTypeName,
151
153
  selectTypeName,
152
154
  whereTypeName,
155
+ ...(conditionTypeName ? [conditionTypeName] : []),
153
156
  orderByTypeName,
154
157
  createInputTypeName,
155
158
  updateInputTypeName,
156
159
  patchTypeName,
157
- ], true));
160
+ ];
161
+ statements.push(createImportDeclaration('../input-types', inputTypeImports, true));
158
162
  statements.push(createImportDeclaration('../input-types', ['connectionFieldsMap']));
159
163
  const classBody = [];
160
164
  // Constructor
@@ -168,11 +172,15 @@ function generateModelFile(table, _useSharedTypes) {
168
172
  const pkTsType = () => tsTypeFromPrimitive(pkField.tsType);
169
173
  // ── findMany ───────────────────────────────────────────────────────────
170
174
  {
171
- const argsType = (sel) => t.tsTypeReference(t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation([
172
- sel,
173
- t.tsTypeReference(t.identifier(whereTypeName)),
174
- t.tsTypeReference(t.identifier(orderByTypeName)),
175
- ]));
175
+ const findManyTypeArgs = [
176
+ (sel) => sel,
177
+ () => t.tsTypeReference(t.identifier(whereTypeName)),
178
+ ...(conditionTypeName
179
+ ? [() => t.tsTypeReference(t.identifier(conditionTypeName))]
180
+ : []),
181
+ () => t.tsTypeReference(t.identifier(orderByTypeName)),
182
+ ];
183
+ const argsType = (sel) => t.tsTypeReference(t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation(findManyTypeArgs.map(fn => fn(sel))));
176
184
  const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
177
185
  t.tsTypeLiteral([
178
186
  t.tsPropertySignature(t.identifier(pluralQueryName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier('ConnectionResult'), t.tsTypeParameterInstantiation([
@@ -190,34 +198,47 @@ function generateModelFile(table, _useSharedTypes) {
190
198
  strictSelectGuard(selectTypeName),
191
199
  ]));
192
200
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
201
+ const findManyObjProps = [
202
+ t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
203
+ ...(conditionTypeName
204
+ ? [
205
+ t.objectProperty(t.identifier('condition'), t.optionalMemberExpression(t.identifier('args'), t.identifier('condition'), false, true)),
206
+ ]
207
+ : []),
208
+ t.objectProperty(t.identifier('orderBy'), t.tsAsExpression(t.optionalMemberExpression(t.identifier('args'), t.identifier('orderBy'), false, true), t.tsUnionType([
209
+ t.tsArrayType(t.tsStringKeyword()),
210
+ t.tsUndefinedKeyword(),
211
+ ]))),
212
+ t.objectProperty(t.identifier('first'), t.optionalMemberExpression(t.identifier('args'), t.identifier('first'), false, true)),
213
+ t.objectProperty(t.identifier('last'), t.optionalMemberExpression(t.identifier('args'), t.identifier('last'), false, true)),
214
+ t.objectProperty(t.identifier('after'), t.optionalMemberExpression(t.identifier('args'), t.identifier('after'), false, true)),
215
+ t.objectProperty(t.identifier('before'), t.optionalMemberExpression(t.identifier('args'), t.identifier('before'), false, true)),
216
+ t.objectProperty(t.identifier('offset'), t.optionalMemberExpression(t.identifier('args'), t.identifier('offset'), false, true)),
217
+ ];
193
218
  const bodyArgs = [
194
219
  t.stringLiteral(typeName),
195
220
  t.stringLiteral(pluralQueryName),
196
221
  selectExpr,
197
- t.objectExpression([
198
- t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
199
- t.objectProperty(t.identifier('orderBy'), t.tsAsExpression(t.optionalMemberExpression(t.identifier('args'), t.identifier('orderBy'), false, true), t.tsUnionType([
200
- t.tsArrayType(t.tsStringKeyword()),
201
- t.tsUndefinedKeyword(),
202
- ]))),
203
- t.objectProperty(t.identifier('first'), t.optionalMemberExpression(t.identifier('args'), t.identifier('first'), false, true)),
204
- t.objectProperty(t.identifier('last'), t.optionalMemberExpression(t.identifier('args'), t.identifier('last'), false, true)),
205
- t.objectProperty(t.identifier('after'), t.optionalMemberExpression(t.identifier('args'), t.identifier('after'), false, true)),
206
- t.objectProperty(t.identifier('before'), t.optionalMemberExpression(t.identifier('args'), t.identifier('before'), false, true)),
207
- t.objectProperty(t.identifier('offset'), t.optionalMemberExpression(t.identifier('args'), t.identifier('offset'), false, true)),
208
- ]),
222
+ t.objectExpression(findManyObjProps),
209
223
  t.stringLiteral(whereTypeName),
210
224
  t.stringLiteral(orderByTypeName),
211
225
  t.identifier('connectionFieldsMap'),
226
+ ...(conditionTypeName
227
+ ? [t.stringLiteral(conditionTypeName)]
228
+ : []),
212
229
  ];
213
230
  classBody.push(createClassMethod('findMany', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindManyDocument', bodyArgs, 'query', typeName, pluralQueryName)));
214
231
  }
215
232
  // ── findFirst ──────────────────────────────────────────────────────────
216
233
  {
217
- const argsType = (sel) => t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
218
- sel,
219
- t.tsTypeReference(t.identifier(whereTypeName)),
220
- ]));
234
+ const findFirstTypeArgs = [
235
+ (sel) => sel,
236
+ () => t.tsTypeReference(t.identifier(whereTypeName)),
237
+ ...(conditionTypeName
238
+ ? [() => t.tsTypeReference(t.identifier(conditionTypeName))]
239
+ : []),
240
+ ];
241
+ const argsType = (sel) => t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation(findFirstTypeArgs.map(fn => fn(sel))));
221
242
  const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
222
243
  t.tsTypeLiteral([
223
244
  t.tsPropertySignature(t.identifier(pluralQueryName), t.tsTypeAnnotation(t.tsTypeLiteral([
@@ -235,15 +256,24 @@ function generateModelFile(table, _useSharedTypes) {
235
256
  strictSelectGuard(selectTypeName),
236
257
  ]));
237
258
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
259
+ const findFirstObjProps = [
260
+ t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
261
+ ...(conditionTypeName
262
+ ? [
263
+ t.objectProperty(t.identifier('condition'), t.optionalMemberExpression(t.identifier('args'), t.identifier('condition'), false, true)),
264
+ ]
265
+ : []),
266
+ ];
238
267
  const bodyArgs = [
239
268
  t.stringLiteral(typeName),
240
269
  t.stringLiteral(pluralQueryName),
241
270
  selectExpr,
242
- t.objectExpression([
243
- t.objectProperty(t.identifier('where'), t.optionalMemberExpression(t.identifier('args'), t.identifier('where'), false, true)),
244
- ]),
271
+ t.objectExpression(findFirstObjProps),
245
272
  t.stringLiteral(whereTypeName),
246
273
  t.identifier('connectionFieldsMap'),
274
+ ...(conditionTypeName
275
+ ? [t.stringLiteral(conditionTypeName)]
276
+ : []),
247
277
  ];
248
278
  classBody.push(createClassMethod('findFirst', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindFirstDocument', bodyArgs, 'query', typeName, pluralQueryName)));
249
279
  }
@@ -447,6 +477,6 @@ function generateModelFile(table, _useSharedTypes) {
447
477
  tableName: table.name,
448
478
  };
449
479
  }
450
- function generateAllModelFiles(tables, useSharedTypes) {
451
- return tables.map((table) => generateModelFile(table, useSharedTypes));
480
+ function generateAllModelFiles(tables, useSharedTypes, options) {
481
+ return tables.map((table) => generateModelFile(table, useSharedTypes, options));
452
482
  }
@@ -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 = never>(
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 = never>(
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({
@@ -21,9 +21,10 @@ export interface PageInfo {
21
21
  endCursor?: string | null;
22
22
  }
23
23
 
24
- export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
24
+ export interface FindManyArgs<TSelect, TWhere, TCondition = never, TOrderBy = never> {
25
25
  select?: TSelect;
26
26
  where?: TWhere;
27
+ condition?: TCondition;
27
28
  orderBy?: TOrderBy[];
28
29
  first?: number;
29
30
  last?: number;
@@ -32,9 +33,10 @@ export interface FindManyArgs<TSelect, TWhere, TOrderBy> {
32
33
  offset?: number;
33
34
  }
34
35
 
35
- export interface FindFirstArgs<TSelect, TWhere> {
36
+ export interface FindFirstArgs<TSelect, TWhere, TCondition = never> {
36
37
  select?: TSelect;
37
38
  where?: TWhere;
39
+ condition?: TCondition;
38
40
  }
39
41
 
40
42
  export interface CreateArgs<TSelect, TData> {
package/core/generate.js CHANGED
@@ -316,18 +316,18 @@ async function generate(options = {}, internalOptions) {
316
316
  ? config.cli.toolName
317
317
  : 'app';
318
318
  if (docsConfig.readme) {
319
- const readme = (0, docs_generator_1.generateReadme)(tables, allCustomOps, toolName);
319
+ const readme = (0, docs_generator_1.generateReadme)(tables, allCustomOps, toolName, customOperations.typeRegistry);
320
320
  filesToWrite.push({ path: node_path_1.default.posix.join('cli', readme.fileName), content: readme.content });
321
321
  }
322
322
  if (docsConfig.agents) {
323
- const agents = (0, docs_generator_1.generateAgentsDocs)(tables, allCustomOps, toolName);
323
+ const agents = (0, docs_generator_1.generateAgentsDocs)(tables, allCustomOps, toolName, customOperations.typeRegistry);
324
324
  filesToWrite.push({ path: node_path_1.default.posix.join('cli', agents.fileName), content: agents.content });
325
325
  }
326
326
  if (docsConfig.mcp) {
327
- allMcpTools.push(...(0, docs_generator_1.getCliMcpTools)(tables, allCustomOps, toolName));
327
+ allMcpTools.push(...(0, docs_generator_1.getCliMcpTools)(tables, allCustomOps, toolName, customOperations.typeRegistry));
328
328
  }
329
329
  if (docsConfig.skills) {
330
- for (const skill of (0, docs_generator_1.generateSkills)(tables, allCustomOps, toolName, targetName)) {
330
+ for (const skill of (0, docs_generator_1.generateSkills)(tables, allCustomOps, toolName, targetName, customOperations.typeRegistry)) {
331
331
  skillsToWrite.push({ path: skill.fileName, content: skill.content });
332
332
  }
333
333
  }
@@ -616,9 +616,19 @@ async function generateMulti(options) {
616
616
  const docsConfig = (0, docs_utils_1.resolveDocsConfig)(firstTargetDocsConfig);
617
617
  const { resolveBuiltinNames } = await Promise.resolve().then(() => __importStar(require('./codegen/cli')));
618
618
  const builtinNames = resolveBuiltinNames(cliTargets.map((t) => t.name), cliConfig.builtinNames);
619
+ // Merge all target type registries into a combined registry for docs generation
620
+ const combinedRegistry = new Map();
621
+ for (const t of cliTargets) {
622
+ if (t.typeRegistry) {
623
+ for (const [key, value] of t.typeRegistry) {
624
+ combinedRegistry.set(key, value);
625
+ }
626
+ }
627
+ }
619
628
  const docsInput = {
620
629
  toolName,
621
630
  builtinNames,
631
+ registry: combinedRegistry.size > 0 ? combinedRegistry : undefined,
622
632
  targets: cliTargets.map((t) => ({
623
633
  name: t.name,
624
634
  endpoint: t.endpoint,