@constructive-io/graphql-codegen 4.26.0 → 4.27.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 (41) hide show
  1. package/core/codegen/cli/command-map-generator.js +9 -9
  2. package/core/codegen/cli/custom-command-generator.js +2 -2
  3. package/core/codegen/cli/docs-generator.js +23 -31
  4. package/core/codegen/cli/index.d.ts +3 -1
  5. package/core/codegen/cli/index.js +16 -1
  6. package/core/codegen/cli/table-command-generator.d.ts +2 -0
  7. package/core/codegen/cli/table-command-generator.js +250 -36
  8. package/core/codegen/cli/utils-generator.d.ts +8 -0
  9. package/core/codegen/cli/utils-generator.js +14 -0
  10. package/core/codegen/docs-utils.d.ts +17 -0
  11. package/core/codegen/docs-utils.js +127 -0
  12. package/core/codegen/hooks-docs-generator.js +3 -3
  13. package/core/codegen/index.js +1 -1
  14. package/core/codegen/orm/docs-generator.js +3 -3
  15. package/core/codegen/orm/index.js +1 -1
  16. package/core/codegen/orm/input-types-generator.js +1 -1
  17. package/core/codegen/orm/model-generator.js +1 -1
  18. package/core/codegen/queries.js +1 -1
  19. package/core/codegen/templates/cli-utils.ts +11 -11
  20. package/core/codegen/templates/embedder.ts +175 -0
  21. package/core/generate.js +2 -0
  22. package/esm/core/codegen/cli/command-map-generator.js +1 -1
  23. package/esm/core/codegen/cli/custom-command-generator.js +1 -1
  24. package/esm/core/codegen/cli/docs-generator.js +7 -15
  25. package/esm/core/codegen/cli/index.d.ts +3 -1
  26. package/esm/core/codegen/cli/index.js +16 -2
  27. package/esm/core/codegen/cli/table-command-generator.d.ts +2 -0
  28. package/esm/core/codegen/cli/table-command-generator.js +250 -36
  29. package/esm/core/codegen/cli/utils-generator.d.ts +8 -0
  30. package/esm/core/codegen/cli/utils-generator.js +13 -0
  31. package/esm/core/codegen/docs-utils.d.ts +17 -0
  32. package/esm/core/codegen/docs-utils.js +125 -0
  33. package/esm/core/codegen/hooks-docs-generator.js +1 -1
  34. package/esm/core/codegen/index.js +1 -1
  35. package/esm/core/codegen/orm/docs-generator.js +1 -1
  36. package/esm/core/codegen/orm/index.js +1 -1
  37. package/esm/core/codegen/orm/input-types-generator.js +1 -1
  38. package/esm/core/codegen/orm/model-generator.js +1 -1
  39. package/esm/core/codegen/queries.js +1 -1
  40. package/esm/core/generate.js +2 -0
  41. package/package.json +8 -9
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getFieldsWithDefaults = getFieldsWithDefaults;
37
37
  exports.generateTableCommand = generateTableCommand;
38
38
  const t = __importStar(require("@babel/types"));
39
- const komoji_1 = require("komoji");
39
+ const inflekt_1 = require("inflekt");
40
40
  const babel_ast_1 = require("../babel-ast");
41
41
  const utils_1 = require("../utils");
42
42
  const docs_utils_1 = require("../docs-utils");
@@ -202,7 +202,146 @@ function buildSubcommandSwitch(subcommands, handlerPrefix, usageVarName) {
202
202
  ]));
203
203
  return t.switchStatement(t.identifier('subcommand'), cases);
204
204
  }
205
- function buildListHandler(table, targetName, typeRegistry) {
205
+ /**
206
+ * Build an auto-embed block that resolves the embedder and converts text
207
+ * values in the where clause to vector embeddings.
208
+ *
209
+ * Generates code equivalent to:
210
+ * if (argv['auto-embed']) {
211
+ * const embedder = resolveEmbedder();
212
+ * if (!embedder) {
213
+ * console.error('--auto-embed requires an embedder. Set EMBEDDER_PROVIDER=ollama');
214
+ * process.exit(1);
215
+ * }
216
+ * await autoEmbedWhere(findManyArgs.where ?? {}, ['fieldA', 'fieldB'], embedder);
217
+ * }
218
+ *
219
+ * @param whereExpr - The expression to access the where clause (e.g. findManyArgs.where, searchWhere)
220
+ * @param vectorFieldNames - Names of vector embedding fields detected at codegen time
221
+ * @param assignBack - If true, assigns the result back to findManyArgs.where
222
+ */
223
+ function buildAutoEmbedBlock(whereExpr, vectorFieldNames, assignBack = false) {
224
+ const fieldNamesArray = t.arrayExpression(vectorFieldNames.map((n) => t.stringLiteral(n)));
225
+ const embedderDecl = t.variableDeclaration('const', [
226
+ t.variableDeclarator(t.identifier('embedder'), t.callExpression(t.identifier('resolveEmbedder'), [])),
227
+ ]);
228
+ const noEmbedderCheck = t.ifStatement(t.unaryExpression('!', t.identifier('embedder')), t.blockStatement([
229
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [
230
+ t.stringLiteral('--auto-embed requires an embedder. Set EMBEDDER_PROVIDER=ollama (and optionally EMBEDDER_MODEL, EMBEDDER_BASE_URL).'),
231
+ ])),
232
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
233
+ ]));
234
+ const autoEmbedCall = t.awaitExpression(t.callExpression(t.identifier('autoEmbedWhere'), [
235
+ t.logicalExpression('??', whereExpr, t.objectExpression([])),
236
+ fieldNamesArray,
237
+ t.identifier('embedder'),
238
+ ]));
239
+ const bodyStatements = [embedderDecl, noEmbedderCheck];
240
+ if (assignBack) {
241
+ // findManyArgs.where = await autoEmbedWhere(findManyArgs.where ?? {}, [...], embedder);
242
+ bodyStatements.push(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.identifier('findManyArgs'), t.identifier('where')), autoEmbedCall)));
243
+ }
244
+ else {
245
+ // await autoEmbedWhere(searchWhere, [...], embedder);
246
+ bodyStatements.push(t.expressionStatement(autoEmbedCall));
247
+ }
248
+ return t.ifStatement(t.memberExpression(t.identifier('argv'), t.stringLiteral('auto-embed'), true), t.blockStatement(bodyStatements));
249
+ }
250
+ /**
251
+ * Build an auto-embed block for mutation handlers (create/update).
252
+ *
253
+ * Generates code equivalent to:
254
+ * if (argv['auto-embed']) {
255
+ * const embedder = resolveEmbedder();
256
+ * if (!embedder) {
257
+ * console.error('--auto-embed requires an embedder. Set EMBEDDER_PROVIDER=ollama');
258
+ * process.exit(1);
259
+ * }
260
+ * await autoEmbedInput(cleanedData, ['embedding'], embedder);
261
+ * }
262
+ *
263
+ * @param vectorFieldNames - Names of vector embedding fields detected at codegen time
264
+ */
265
+ function buildAutoEmbedInputBlock(vectorFieldNames) {
266
+ const fieldNamesArray = t.arrayExpression(vectorFieldNames.map((n) => t.stringLiteral(n)));
267
+ const embedderDecl = t.variableDeclaration('const', [
268
+ t.variableDeclarator(t.identifier('embedder'), t.callExpression(t.identifier('resolveEmbedder'), [])),
269
+ ]);
270
+ const noEmbedderCheck = t.ifStatement(t.unaryExpression('!', t.identifier('embedder')), t.blockStatement([
271
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [
272
+ t.stringLiteral('--auto-embed requires an embedder. Set EMBEDDER_PROVIDER=ollama (and optionally EMBEDDER_MODEL, EMBEDDER_BASE_URL).'),
273
+ ])),
274
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
275
+ ]));
276
+ const autoEmbedCall = t.awaitExpression(t.callExpression(t.identifier('autoEmbedInput'), [
277
+ t.identifier('cleanedData'),
278
+ fieldNamesArray,
279
+ t.identifier('embedder'),
280
+ ]));
281
+ return t.ifStatement(t.memberExpression(t.identifier('argv'), t.stringLiteral('auto-embed'), true), t.blockStatement([
282
+ embedderDecl,
283
+ noEmbedderCheck,
284
+ t.expressionStatement(autoEmbedCall),
285
+ ]));
286
+ }
287
+ /**
288
+ * Build the FindManyArgs type instantiation for a table:
289
+ * FindManyArgs<SelectType, FilterType, ConditionType, OrderByType> & { select: SelectType }
290
+ *
291
+ * The intersection with { select: SelectType } makes select required,
292
+ * matching what the ORM's findMany method expects. parseFindManyArgs
293
+ * always sets select at runtime (from defaultSelect or --select flag).
294
+ */
295
+ function buildFindManyArgsType(table, conditionEnabled) {
296
+ const { typeName } = (0, utils_1.getTableNames)(table);
297
+ const selectTypeName = `${typeName}Select`;
298
+ const whereTypeName = (0, utils_1.getFilterTypeName)(table);
299
+ const conditionTypeName = conditionEnabled ? (0, utils_1.getConditionTypeName)(table) : undefined;
300
+ const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
301
+ const findManyType = t.tsTypeReference(t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation([
302
+ t.tsTypeReference(t.identifier(selectTypeName)),
303
+ t.tsTypeReference(t.identifier(whereTypeName)),
304
+ conditionTypeName
305
+ ? t.tsTypeReference(t.identifier(conditionTypeName))
306
+ : t.tsNeverKeyword(),
307
+ t.tsTypeReference(t.identifier(orderByTypeName)),
308
+ ]));
309
+ // Intersect with { select: SelectType } to make select required
310
+ return t.tsIntersectionType([
311
+ findManyType,
312
+ t.tsTypeLiteral([
313
+ Object.assign(t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(selectTypeName)))), { optional: false }),
314
+ ]),
315
+ ]);
316
+ }
317
+ /**
318
+ * Build the FindFirstArgs type instantiation for a table:
319
+ * FindFirstArgs<SelectType, FilterType, ConditionType> & { select: SelectType }
320
+ *
321
+ * The intersection with { select: SelectType } makes select required,
322
+ * matching what the ORM's findFirst method expects.
323
+ */
324
+ function buildFindFirstArgsType(table, conditionEnabled) {
325
+ const { typeName } = (0, utils_1.getTableNames)(table);
326
+ const selectTypeName = `${typeName}Select`;
327
+ const whereTypeName = (0, utils_1.getFilterTypeName)(table);
328
+ const conditionTypeName = conditionEnabled ? (0, utils_1.getConditionTypeName)(table) : undefined;
329
+ const findFirstType = t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
330
+ t.tsTypeReference(t.identifier(selectTypeName)),
331
+ t.tsTypeReference(t.identifier(whereTypeName)),
332
+ conditionTypeName
333
+ ? t.tsTypeReference(t.identifier(conditionTypeName))
334
+ : t.tsNeverKeyword(),
335
+ ]));
336
+ // Intersect with { select: SelectType } to make select required
337
+ return t.tsIntersectionType([
338
+ findFirstType,
339
+ t.tsTypeLiteral([
340
+ Object.assign(t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(selectTypeName)))), { optional: false }),
341
+ ]),
342
+ ]);
343
+ }
344
+ function buildListHandler(table, vectorFieldNames, targetName, typeRegistry, conditionEnabled = false) {
206
345
  const { singularName } = (0, utils_1.getTableNames)(table);
207
346
  const defaultSelectObj = buildSelectObject(table, typeRegistry);
208
347
  // --- Build the try body ---
@@ -211,13 +350,23 @@ function buildListHandler(table, targetName, typeRegistry) {
211
350
  tryBody.push(t.variableDeclaration('const', [
212
351
  t.variableDeclarator(t.identifier('defaultSelect'), defaultSelectObj),
213
352
  ]));
214
- // const findManyArgs = parseFindManyArgs(argv, defaultSelect);
215
- tryBody.push(t.variableDeclaration('const', [
216
- t.variableDeclarator(t.identifier('findManyArgs'), t.callExpression(t.identifier('parseFindManyArgs'), [
353
+ // const findManyArgs = parseFindManyArgs<FindManyArgs<...>>(argv, defaultSelect);
354
+ {
355
+ const callExpr = t.callExpression(t.identifier('parseFindManyArgs'), [
217
356
  t.identifier('argv'),
218
357
  t.identifier('defaultSelect'),
219
- ])),
220
- ]));
358
+ ]);
359
+ callExpr.typeParameters = t.tsTypeParameterInstantiation([
360
+ buildFindManyArgsType(table, conditionEnabled),
361
+ ]);
362
+ tryBody.push(t.variableDeclaration('const', [
363
+ t.variableDeclarator(t.identifier('findManyArgs'), callExpr),
364
+ ]));
365
+ }
366
+ // Auto-embed vector fields in the where clause when --auto-embed is passed
367
+ if (vectorFieldNames.length > 0) {
368
+ tryBody.push(buildAutoEmbedBlock(t.memberExpression(t.identifier('findManyArgs'), t.identifier('where')), vectorFieldNames, true));
369
+ }
221
370
  tryBody.push(buildGetClientStatement(targetName));
222
371
  // const result = await client.<singular>.findMany(findManyArgs).execute();
223
372
  tryBody.push(t.variableDeclaration('const', [
@@ -234,10 +383,10 @@ function buildListHandler(table, targetName, typeRegistry) {
234
383
  }
235
384
  /**
236
385
  * Build a `handleFindFirst` function — CLI equivalent of the TS SDK's findFirst().
237
- * Accepts --fields, --where.<field>.<op>, --condition.<field>.<op> flags.
386
+ * Accepts --select, --where.<field>.<op>, --condition.<field>.<op> flags.
238
387
  * Internally calls findMany with first:1 and returns a single record (or null).
239
388
  */
240
- function buildFindFirstHandler(table, targetName, typeRegistry) {
389
+ function buildFindFirstHandler(table, targetName, typeRegistry, conditionEnabled = false) {
241
390
  const { singularName } = (0, utils_1.getTableNames)(table);
242
391
  const defaultSelectObj = buildSelectObject(table, typeRegistry);
243
392
  const tryBody = [];
@@ -245,13 +394,19 @@ function buildFindFirstHandler(table, targetName, typeRegistry) {
245
394
  tryBody.push(t.variableDeclaration('const', [
246
395
  t.variableDeclarator(t.identifier('defaultSelect'), defaultSelectObj),
247
396
  ]));
248
- // const findFirstArgs = parseFindFirstArgs(argv, defaultSelect);
249
- tryBody.push(t.variableDeclaration('const', [
250
- t.variableDeclarator(t.identifier('findFirstArgs'), t.callExpression(t.identifier('parseFindFirstArgs'), [
397
+ // const findFirstArgs = parseFindFirstArgs<FindFirstArgs<...>>(argv, defaultSelect);
398
+ {
399
+ const callExpr = t.callExpression(t.identifier('parseFindFirstArgs'), [
251
400
  t.identifier('argv'),
252
401
  t.identifier('defaultSelect'),
253
- ])),
254
- ]));
402
+ ]);
403
+ callExpr.typeParameters = t.tsTypeParameterInstantiation([
404
+ buildFindFirstArgsType(table, conditionEnabled),
405
+ ]);
406
+ tryBody.push(t.variableDeclaration('const', [
407
+ t.variableDeclarator(t.identifier('findFirstArgs'), callExpr),
408
+ ]));
409
+ }
255
410
  tryBody.push(buildGetClientStatement(targetName));
256
411
  // const result = await client.<singular>.findFirst(findFirstArgs).execute();
257
412
  tryBody.push(t.variableDeclaration('const', [
@@ -270,9 +425,9 @@ function buildFindFirstHandler(table, targetName, typeRegistry) {
270
425
  * Build a `handleSearch` function for tables with search-capable fields.
271
426
  * Extracts the first positional arg as the query string and auto-builds a
272
427
  * `where` clause that targets all detected search fields (tsvector, BM25,
273
- * trigram, vector embedding). Supports --limit, --offset, --fields, --orderBy.
428
+ * trigram, vector embedding). Supports --limit, --offset, --select, --orderBy.
274
429
  */
275
- function buildSearchHandler(table, specialGroups, targetName, typeRegistry) {
430
+ function buildSearchHandler(table, specialGroups, vectorFieldNames, targetName, typeRegistry, conditionEnabled = false) {
276
431
  const { singularName } = (0, utils_1.getTableNames)(table);
277
432
  const defaultSelectObj = buildSelectObject(table, typeRegistry);
278
433
  const tryBody = [];
@@ -288,7 +443,7 @@ function buildSearchHandler(table, specialGroups, targetName, typeRegistry) {
288
443
  t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
289
444
  ])));
290
445
  // Build the where clause properties from detected search fields
291
- // e.g. { tsvContent: { query }, bm25Body: { query }, trgmTitle: { value: query, threshold: 0.3 }, vectorEmbedding: { value: query } }
446
+ // e.g. { tsvContent: { query }, bm25Body: { query }, trgmTitle: { value: query, threshold: 0.3 }, vectorEmbedding: { vector: query } }
292
447
  const whereProps = [];
293
448
  for (const group of specialGroups) {
294
449
  for (const field of group.fields) {
@@ -318,9 +473,10 @@ function buildSearchHandler(table, specialGroups, targetName, typeRegistry) {
318
473
  ])));
319
474
  }
320
475
  else if (group.category === 'embedding') {
321
- // Vector embedding field: { value: query }
476
+ // Vector embedding field: { vector: query }
477
+ // When --auto-embed is used, autoEmbedWhere will convert the text to a vector
322
478
  whereProps.push(t.objectProperty(t.identifier(field.name), t.objectExpression([
323
- t.objectProperty(t.identifier('value'), t.identifier('query')),
479
+ t.objectProperty(t.identifier('vector'), t.identifier('query')),
324
480
  ])));
325
481
  }
326
482
  }
@@ -329,18 +485,28 @@ function buildSearchHandler(table, specialGroups, targetName, typeRegistry) {
329
485
  tryBody.push(t.variableDeclaration('const', [
330
486
  t.variableDeclarator(t.identifier('searchWhere'), t.objectExpression(whereProps)),
331
487
  ]));
488
+ // Auto-embed vector fields in the where clause when --auto-embed is passed
489
+ if (vectorFieldNames.length > 0) {
490
+ tryBody.push(buildAutoEmbedBlock(t.identifier('searchWhere'), vectorFieldNames, false));
491
+ }
332
492
  // const defaultSelect = { ... };
333
493
  tryBody.push(t.variableDeclaration('const', [
334
494
  t.variableDeclarator(t.identifier('defaultSelect'), defaultSelectObj),
335
495
  ]));
336
- // const findManyArgs = parseFindManyArgs(argv, defaultSelect, searchWhere);
337
- tryBody.push(t.variableDeclaration('const', [
338
- t.variableDeclarator(t.identifier('findManyArgs'), t.callExpression(t.identifier('parseFindManyArgs'), [
496
+ // const findManyArgs = parseFindManyArgs<FindManyArgs<...>>(argv, defaultSelect, searchWhere);
497
+ {
498
+ const callExpr = t.callExpression(t.identifier('parseFindManyArgs'), [
339
499
  t.identifier('argv'),
340
500
  t.identifier('defaultSelect'),
341
501
  t.identifier('searchWhere'),
342
- ])),
343
- ]));
502
+ ]);
503
+ callExpr.typeParameters = t.tsTypeParameterInstantiation([
504
+ buildFindManyArgsType(table, conditionEnabled),
505
+ ]);
506
+ tryBody.push(t.variableDeclaration('const', [
507
+ t.variableDeclarator(t.identifier('findManyArgs'), callExpr),
508
+ ]));
509
+ }
344
510
  tryBody.push(buildGetClientStatement(targetName));
345
511
  // const result = await client.<singular>.findMany(findManyArgs).execute();
346
512
  tryBody.push(t.variableDeclaration('const', [
@@ -412,7 +578,7 @@ function getFieldsWithDefaults(table, typeRegistry) {
412
578
  }
413
579
  return fieldsWithDefaults;
414
580
  }
415
- function buildMutationHandler(table, operation, targetName, typeRegistry, ormTypes) {
581
+ function buildMutationHandler(table, operation, vectorFieldNames, targetName, typeRegistry, ormTypes) {
416
582
  const { singularName } = (0, utils_1.getTableNames)(table);
417
583
  const pkFields = (0, utils_1.getPrimaryKeyInfo)(table);
418
584
  const pk = pkFields[0];
@@ -540,6 +706,11 @@ function buildMutationHandler(table, operation, targetName, typeRegistry, ormTyp
540
706
  tryBody.push(t.variableDeclaration('const', [
541
707
  t.variableDeclarator(t.identifier('cleanedData'), cleanedDataExpr),
542
708
  ]));
709
+ // Inject --auto-embed block for create/update when table has vector fields.
710
+ // Converts text strings in vector fields to embeddings before the ORM call.
711
+ if (vectorFieldNames.length > 0) {
712
+ tryBody.push(buildAutoEmbedInputBlock(vectorFieldNames));
713
+ }
543
714
  }
544
715
  tryBody.push(buildGetClientStatement(targetName), t.variableDeclaration('const', [
545
716
  t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, operation, ormArgs))),
@@ -554,8 +725,8 @@ function buildMutationHandler(table, operation, targetName, typeRegistry, ormTyp
554
725
  ]), false, true);
555
726
  }
556
727
  function generateTableCommand(table, options) {
557
- const { singularName } = (0, utils_1.getTableNames)(table);
558
- const commandName = (0, komoji_1.toKebabCase)(singularName);
728
+ const { singularName, typeName } = (0, utils_1.getTableNames)(table);
729
+ const commandName = (0, inflekt_1.toKebabCase)(singularName);
559
730
  const statements = [];
560
731
  const executorPath = options?.executorImportPath ?? '../executor';
561
732
  statements.push(createImportDeclaration('inquirerer', [
@@ -579,7 +750,25 @@ function generateTableCommand(table, options) {
579
750
  const inputTypesPath = options?.targetName
580
751
  ? `../../../orm/input-types`
581
752
  : `../../orm/input-types`;
582
- statements.push(createImportDeclaration(inputTypesPath, [createInputTypeName, patchTypeName], true));
753
+ // Import table-specific ORM types for generic type parameters on parseFindManyArgs/parseFindFirstArgs
754
+ const selectTypeName = `${typeName}Select`;
755
+ const whereTypeName = (0, utils_1.getFilterTypeName)(table);
756
+ const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
757
+ const conditionEnabled = options?.condition === true;
758
+ const conditionTypeName = conditionEnabled ? (0, utils_1.getConditionTypeName)(table) : undefined;
759
+ statements.push(createImportDeclaration(inputTypesPath, [
760
+ createInputTypeName,
761
+ patchTypeName,
762
+ selectTypeName,
763
+ whereTypeName,
764
+ ...(conditionTypeName ? [conditionTypeName] : []),
765
+ orderByTypeName,
766
+ ], true));
767
+ // Import FindManyArgs/FindFirstArgs from select-types for proper generic typing
768
+ const selectTypesPath = options?.targetName
769
+ ? `../../../orm/select-types`
770
+ : `../../orm/select-types`;
771
+ statements.push(createImportDeclaration(selectTypesPath, ['FindManyArgs', 'FindFirstArgs'], true));
583
772
  // Generate field schema for type coercion
584
773
  // Use explicit FieldSchema type annotation so TS narrows string literals to FieldType
585
774
  const fieldSchemaId = t.identifier('fieldSchema');
@@ -600,6 +789,21 @@ function generateTableCommand(table, options) {
600
789
  // Detect whether this table has search-capable fields (tsvector, BM25, trgm, vector embedding)
601
790
  const specialGroups = (0, docs_utils_1.categorizeSpecialFields)(table, options?.typeRegistry);
602
791
  const hasSearchFields = specialGroups.some((g) => g.category === 'search' || g.category === 'embedding');
792
+ // Collect vector embedding field names for --auto-embed support
793
+ const vectorFieldNames = [];
794
+ for (const group of specialGroups) {
795
+ if (group.category === 'embedding') {
796
+ for (const field of group.fields) {
797
+ vectorFieldNames.push(field.name);
798
+ }
799
+ }
800
+ }
801
+ const hasEmbeddings = vectorFieldNames.length > 0;
802
+ // Import embedder functions when table has vector embedding fields
803
+ if (hasEmbeddings) {
804
+ const embedderPath = options?.targetName ? '../../embedder' : '../embedder';
805
+ statements.push(createImportDeclaration(embedderPath, ['resolveEmbedder', 'autoEmbedWhere', 'autoEmbedInput']));
806
+ }
603
807
  const subcommands = ['list', 'find-first'];
604
808
  if (hasSearchFields)
605
809
  subcommands.push('search');
@@ -625,11 +829,21 @@ function generateTableCommand(table, options) {
625
829
  usageLines.push(` create Create a new ${singularName}`);
626
830
  if (hasUpdate)
627
831
  usageLines.push(` update Update an existing ${singularName}`);
832
+ if (hasEmbeddings) {
833
+ usageLines.push('', 'Create/Update Options:', ' --auto-embed Convert text values in vector fields to embeddings before saving');
834
+ }
628
835
  if (hasDelete)
629
836
  usageLines.push(` delete Delete a ${singularName}`);
630
- usageLines.push('', 'List Options:', ' --limit <n> Max number of records to return (forward pagination)', ' --last <n> Number of records from the end (backward pagination)', ' --after <cursor> Cursor for forward pagination', ' --before <cursor> Cursor for backward pagination', ' --offset <n> Number of records to skip', ' --fields <fields> Comma-separated list of fields to return', ' --where.<field>.<op> Filter (dot-notation, e.g. --where.name.equalTo foo)', ' --condition.<f>.<op> Condition filter (dot-notation)', ' --orderBy <values> Comma-separated ordering values (e.g. NAME_ASC,CREATED_AT_DESC)', '', 'Find-First Options:', ' --fields <fields> Comma-separated list of fields to return', ' --where.<field>.<op> Filter (dot-notation, e.g. --where.status.equalTo active)', ' --condition.<f>.<op> Condition filter (dot-notation)', '');
837
+ usageLines.push('', 'List Options:', ' --limit <n> Max number of records to return (forward pagination)', ' --last <n> Number of records from the end (backward pagination)', ' --after <cursor> Cursor for forward pagination', ' --before <cursor> Cursor for backward pagination', ' --offset <n> Number of records to skip', ' --select <fields> Comma-separated list of fields to return', ' --where.<field>.<op> Filter (dot-notation, e.g. --where.name.equalTo foo)', ' --condition.<f>.<op> Condition filter (dot-notation)', ' --orderBy <values> Comma-separated ordering values (e.g. NAME_ASC,CREATED_AT_DESC)', '', 'Find-First Options:', ' --select <fields> Comma-separated list of fields to return', ' --where.<field>.<op> Filter (dot-notation, e.g. --where.status.equalTo active)', ' --condition.<f>.<op> Condition filter (dot-notation)', '');
631
838
  if (hasSearchFields) {
632
- usageLines.push('Search Options:', ' <query> Search query string (required)', ' --limit <n> Max number of records to return', ' --offset <n> Number of records to skip', ' --fields <fields> Comma-separated list of fields to return', ' --orderBy <values> Comma-separated list of ordering values', '');
839
+ usageLines.push('Search Options:', ' <query> Search query string (required)', ' --limit <n> Max number of records to return', ' --offset <n> Number of records to skip', ' --select <fields> Comma-separated list of fields to return', ' --orderBy <values> Comma-separated list of ordering values');
840
+ if (hasEmbeddings) {
841
+ usageLines.push(' --auto-embed Convert text queries to vectors via configured embedder');
842
+ }
843
+ usageLines.push('');
844
+ }
845
+ if (hasEmbeddings) {
846
+ usageLines.push('Embedding Options (for --auto-embed):', ' Set EMBEDDER_PROVIDER=ollama to enable text-to-vector embedding.', ' Optional: EMBEDDER_MODEL (default: nomic-embed-text)', ' Optional: EMBEDDER_BASE_URL (default: http://localhost:11434)', '');
633
847
  }
634
848
  usageLines.push(' --help, -h Show this help message', '');
635
849
  statements.push(t.variableDeclaration('const', [
@@ -692,17 +906,17 @@ function generateTableCommand(table, options) {
692
906
  ]), false, true));
693
907
  const tn = options?.targetName;
694
908
  const ormTypes = { createInputTypeName, patchTypeName, innerFieldName };
695
- statements.push(buildListHandler(table, tn, options?.typeRegistry));
696
- statements.push(buildFindFirstHandler(table, tn, options?.typeRegistry));
909
+ statements.push(buildListHandler(table, vectorFieldNames, tn, options?.typeRegistry, conditionEnabled));
910
+ statements.push(buildFindFirstHandler(table, tn, options?.typeRegistry, conditionEnabled));
697
911
  if (hasSearchFields)
698
- statements.push(buildSearchHandler(table, specialGroups, tn, options?.typeRegistry));
912
+ statements.push(buildSearchHandler(table, specialGroups, vectorFieldNames, tn, options?.typeRegistry, conditionEnabled));
699
913
  if (hasGet)
700
914
  statements.push(buildGetHandler(table, tn, options?.typeRegistry));
701
- statements.push(buildMutationHandler(table, 'create', tn, options?.typeRegistry, ormTypes));
915
+ statements.push(buildMutationHandler(table, 'create', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
702
916
  if (hasUpdate)
703
- statements.push(buildMutationHandler(table, 'update', tn, options?.typeRegistry, ormTypes));
917
+ statements.push(buildMutationHandler(table, 'update', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
704
918
  if (hasDelete)
705
- statements.push(buildMutationHandler(table, 'delete', tn, options?.typeRegistry, ormTypes));
919
+ statements.push(buildMutationHandler(table, 'delete', vectorFieldNames, tn, options?.typeRegistry, ormTypes));
706
920
  const header = (0, utils_1.getGeneratedFileHeader)(`CLI commands for ${table.name}`);
707
921
  const code = (0, babel_ast_1.generateCode)(statements);
708
922
  return {
@@ -25,3 +25,11 @@ export declare function generateNodeFetchFile(): GeneratedFile;
25
25
  * provide their own entry point with custom configuration.
26
26
  */
27
27
  export declare function generateEntryPointFile(): GeneratedFile;
28
+ /**
29
+ * Generate an embedder.ts file with pluggable text-to-vector embedding.
30
+ *
31
+ * Provides a runtime embedder registry using @agentic-kit/ollama so that
32
+ * CLI search and list commands can convert text queries into vector arrays
33
+ * for pgvector similarity search when --auto-embed is passed.
34
+ */
35
+ export declare function generateEmbedderFile(): GeneratedFile;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.generateUtilsFile = generateUtilsFile;
37
37
  exports.generateNodeFetchFile = generateNodeFetchFile;
38
38
  exports.generateEntryPointFile = generateEntryPointFile;
39
+ exports.generateEmbedderFile = generateEmbedderFile;
39
40
  const fs = __importStar(require("fs"));
40
41
  const path = __importStar(require("path"));
41
42
  const utils_1 = require("../utils");
@@ -105,3 +106,16 @@ function generateEntryPointFile() {
105
106
  content: readTemplateFile('cli-entry.ts', 'CLI entry point'),
106
107
  };
107
108
  }
109
+ /**
110
+ * Generate an embedder.ts file with pluggable text-to-vector embedding.
111
+ *
112
+ * Provides a runtime embedder registry using @agentic-kit/ollama so that
113
+ * CLI search and list commands can convert text queries into vector arrays
114
+ * for pgvector similarity search when --auto-embed is passed.
115
+ */
116
+ function generateEmbedderFile() {
117
+ return {
118
+ fileName: 'embedder.ts',
119
+ content: readTemplateFile('embedder.ts', 'CLI embedder — pluggable text-to-vector embedding for search commands'),
120
+ };
121
+ }
@@ -65,6 +65,23 @@ export declare function buildSpecialFieldsMarkdown(groups: SpecialFieldGroup[]):
65
65
  * Returns empty array when there are no special fields.
66
66
  */
67
67
  export declare function buildSpecialFieldsPlain(groups: SpecialFieldGroup[]): string[];
68
+ export interface SearchExample {
69
+ description: string;
70
+ code: string[];
71
+ }
72
+ /**
73
+ * Build concrete, field-specific CLI examples for tables with search fields.
74
+ * Uses the same field-name derivation logic as buildSearchHandler in
75
+ * table-command-generator.ts so the examples match the actual generated code.
76
+ *
77
+ * Returns an empty array when the table has no search/embedding fields.
78
+ */
79
+ export declare function buildSearchExamples(specialGroups: SpecialFieldGroup[], toolName: string, cmd: string): SearchExample[];
80
+ /**
81
+ * Build markdown lines for search-specific examples in README-style docs.
82
+ * Returns empty array when there are no search examples.
83
+ */
84
+ export declare function buildSearchExamplesMarkdown(specialGroups: SpecialFieldGroup[], toolName: string, cmd: string): string[];
68
85
  /**
69
86
  * Represents a flattened argument for docs/skills generation.
70
87
  * INPUT_OBJECT args are expanded to dot-notation fields.
@@ -10,6 +10,8 @@ exports.getSearchFields = getSearchFields;
10
10
  exports.categorizeSpecialFields = categorizeSpecialFields;
11
11
  exports.buildSpecialFieldsMarkdown = buildSpecialFieldsMarkdown;
12
12
  exports.buildSpecialFieldsPlain = buildSpecialFieldsPlain;
13
+ exports.buildSearchExamples = buildSearchExamples;
14
+ exports.buildSearchExamplesMarkdown = buildSearchExamplesMarkdown;
13
15
  exports.cleanTypeName = cleanTypeName;
14
16
  exports.flattenArgs = flattenArgs;
15
17
  exports.flattenedArgsToFlags = flattenedArgsToFlags;
@@ -255,6 +257,131 @@ function buildSpecialFieldsPlain(groups) {
255
257
  }
256
258
  return lines;
257
259
  }
260
+ /**
261
+ * Build concrete, field-specific CLI examples for tables with search fields.
262
+ * Uses the same field-name derivation logic as buildSearchHandler in
263
+ * table-command-generator.ts so the examples match the actual generated code.
264
+ *
265
+ * Returns an empty array when the table has no search/embedding fields.
266
+ */
267
+ function buildSearchExamples(specialGroups, toolName, cmd) {
268
+ const examples = [];
269
+ const scoreFields = [];
270
+ for (const group of specialGroups) {
271
+ for (const field of group.fields) {
272
+ // tsvector (FullText scalar) — where input uses the column name directly
273
+ if (field.type.gqlType === 'FullText' && !field.type.isArray) {
274
+ examples.push({
275
+ description: `Full-text search via tsvector (\`${field.name}\`)`,
276
+ code: [
277
+ `${toolName} ${cmd} list --where.${field.name} "search query" --select title,tsvRank`,
278
+ ],
279
+ });
280
+ scoreFields.push('tsvRank');
281
+ }
282
+ // BM25 computed score — bodyBm25Score → bm25Body
283
+ if (/Bm25Score$/.test(field.name)) {
284
+ const baseName = field.name.replace(/Bm25Score$/, '');
285
+ const inputName = `bm25${baseName.charAt(0).toUpperCase()}${baseName.slice(1)}`;
286
+ examples.push({
287
+ description: `BM25 keyword search via \`${inputName}\``,
288
+ code: [
289
+ `${toolName} ${cmd} list --where.${inputName}.query "search query" --select title,${field.name}`,
290
+ ],
291
+ });
292
+ scoreFields.push(field.name);
293
+ }
294
+ // Trigram similarity — titleTrgmSimilarity → trgmTitle
295
+ if (/TrgmSimilarity$/.test(field.name)) {
296
+ const baseName = field.name.replace(/TrgmSimilarity$/, '');
297
+ const inputName = `trgm${baseName.charAt(0).toUpperCase()}${baseName.slice(1)}`;
298
+ examples.push({
299
+ description: `Fuzzy search via trigram similarity (\`${inputName}\`)`,
300
+ code: [
301
+ `${toolName} ${cmd} list --where.${inputName}.value "approximate query" --where.${inputName}.threshold 0.3 --select title,${field.name}`,
302
+ ],
303
+ });
304
+ scoreFields.push(field.name);
305
+ }
306
+ // pgvector embedding — uses column name, with --auto-embed for text-to-vector
307
+ if (group.category === 'embedding') {
308
+ examples.push({
309
+ description: `Vector similarity search via \`${field.name}\` (manual vector)`,
310
+ code: [
311
+ `# Pass a pre-computed vector array via dot-notation`,
312
+ `${toolName} ${cmd} list --where.${field.name}.vector '[0.1,0.2,0.3]' --where.${field.name}.distance 1.0 --select title,${field.name}VectorDistance`,
313
+ ],
314
+ });
315
+ examples.push({
316
+ description: `Vector semantic search via \`${field.name}\` with --auto-embed`,
317
+ code: [
318
+ `# --auto-embed converts text to vectors using the configured embedder (e.g. Ollama nomic-embed-text)`,
319
+ `EMBEDDER_PROVIDER=ollama ${toolName} ${cmd} search "semantic query" --auto-embed --select title,${field.name}VectorDistance`,
320
+ `EMBEDDER_PROVIDER=ollama ${toolName} ${cmd} list --where.${field.name}.vector "semantic query" --auto-embed --select title,${field.name}VectorDistance`,
321
+ ],
322
+ });
323
+ examples.push({
324
+ description: `Create/update with auto-embedded \`${field.name}\` via --auto-embed`,
325
+ code: [
326
+ `# --auto-embed on create/update converts text strings in vector fields to embeddings before saving`,
327
+ `EMBEDDER_PROVIDER=ollama ${toolName} ${cmd} create --${field.name} "text to embed" --auto-embed`,
328
+ `EMBEDDER_PROVIDER=ollama ${toolName} ${cmd} update --${field.name} "new text to embed" --auto-embed`,
329
+ ],
330
+ });
331
+ }
332
+ // searchScore — composite blend field, useful for ordering
333
+ if (field.name === 'searchScore') {
334
+ scoreFields.push('searchScore');
335
+ }
336
+ }
337
+ }
338
+ // Composite fullTextSearch example (dispatches to all text adapters)
339
+ const hasTextSearch = specialGroups.some((g) => g.category === 'search' && g.fields.some((f) => f.type.gqlType === 'FullText' || /TrgmSimilarity$/.test(f.name) || /Bm25Score$/.test(f.name)));
340
+ if (hasTextSearch) {
341
+ const fieldsArg = scoreFields.length > 0
342
+ ? `title,${[...new Set(scoreFields)].join(',')}`
343
+ : 'title';
344
+ examples.push({
345
+ description: 'Composite search (fullTextSearch dispatches to all text adapters)',
346
+ code: [
347
+ `${toolName} ${cmd} list --where.fullTextSearch "search query" --select ${fieldsArg}`,
348
+ ],
349
+ });
350
+ }
351
+ // Combined search + pagination + ordering example
352
+ if (examples.length > 0) {
353
+ examples.push({
354
+ description: 'Search with pagination and field projection',
355
+ code: [
356
+ `${toolName} ${cmd} list --where.fullTextSearch "query" --limit 10 --select id,title,searchScore`,
357
+ `${toolName} ${cmd} search "query" --limit 10 --select id,title,searchScore`,
358
+ ],
359
+ });
360
+ }
361
+ return examples;
362
+ }
363
+ /**
364
+ * Build markdown lines for search-specific examples in README-style docs.
365
+ * Returns empty array when there are no search examples.
366
+ */
367
+ function buildSearchExamplesMarkdown(specialGroups, toolName, cmd) {
368
+ const examples = buildSearchExamples(specialGroups, toolName, cmd);
369
+ if (examples.length === 0)
370
+ return [];
371
+ const lines = [];
372
+ lines.push('**Search Examples:**');
373
+ lines.push('');
374
+ for (const ex of examples) {
375
+ lines.push(`*${ex.description}:*`);
376
+ lines.push('```bash');
377
+ for (const c of ex.code) {
378
+ lines.push(c);
379
+ }
380
+ lines.push('```');
381
+ lines.push('');
382
+ }
383
+ return lines;
384
+ }
258
385
  function unwrapNonNull(typeRef) {
259
386
  if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
260
387
  return { inner: typeRef.ofType, required: true };
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateHooksReadme = generateHooksReadme;
4
4
  exports.generateHooksAgentsDocs = generateHooksAgentsDocs;
5
5
  exports.generateHooksSkills = generateHooksSkills;
6
- const komoji_1 = require("komoji");
6
+ const inflekt_1 = require("inflekt");
7
7
  const docs_utils_1 = require("./docs-utils");
8
8
  const utils_1 = require("./utils");
9
9
  function getCustomHookName(op) {
@@ -178,7 +178,7 @@ function generateHooksSkills(tables, customOperations, targetName, registry) {
178
178
  const selectFields = scalarFields
179
179
  .map((f) => `${f.name}: true`)
180
180
  .join(', ');
181
- const refName = (0, komoji_1.toKebabCase)(singularName);
181
+ const refName = (0, inflekt_1.toKebabCase)(singularName);
182
182
  referenceNames.push(refName);
183
183
  files.push({
184
184
  fileName: `${skillName}/references/${refName}.md`,
@@ -229,7 +229,7 @@ function generateHooksSkills(tables, customOperations, targetName, registry) {
229
229
  const callArgs = op.args.length > 0
230
230
  ? `{ ${op.args.map((a) => `${a.name}: ${(0, docs_utils_1.argPlaceholder)(a, registry)}`).join(', ')} }`
231
231
  : '';
232
- const refName = (0, komoji_1.toKebabCase)(op.name);
232
+ const refName = (0, inflekt_1.toKebabCase)(op.name);
233
233
  referenceNames.push(refName);
234
234
  files.push({
235
235
  fileName: `${skillName}/references/${refName}.md`,