Skip to content

Commit 8c1724d

Browse files
committed
feat(codegen): generate field-specific search examples in CLI docs
When a table has search-capable fields (tsvector, trgm, BM25, pgvector), the generated README and skill references now include concrete CLI examples showing the exact dot-notation flags for each search type: - tsvector: --where.<field> "query" - trgm: --where.trgm<Base>.value "query" --where.trgm<Base>.threshold 0.3 - BM25: --where.bm25<Base>.query "query" - pgvector: --where.<field>.vector '[...]' --where.<field>.distance 1.0 - composite: --where.fullTextSearch "query" Also adds a combined search + pagination example. Field-name derivation mirrors buildSearchHandler so examples always match the generated code. Integrated into all four generators: single-target README, single-target skills, multi-target README, and multi-target skills.
1 parent 046b971 commit 8c1724d

2 files changed

Lines changed: 145 additions & 12 deletions

File tree

graphql/codegen/src/core/codegen/cli/docs-generator.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
getSearchFields,
1111
categorizeSpecialFields,
1212
buildSpecialFieldsMarkdown,
13+
buildSearchExamples,
14+
buildSearchExamplesMarkdown,
1315
getReadmeHeader,
1416
getReadmeFooter,
1517
gqlTypeToJsonSchemaType,
@@ -160,6 +162,7 @@ export function generateReadme(
160162
}
161163
const specialGroups = categorizeSpecialFields(table, registry);
162164
lines.push(...buildSpecialFieldsMarkdown(specialGroups));
165+
lines.push(...buildSearchExamplesMarkdown(specialGroups, toolName, kebab));
163166
lines.push('');
164167
}
165168
}
@@ -430,12 +433,7 @@ export function generateSkills(
430433
description: `List ${singularName} records with filtering and ordering`,
431434
code: [`${toolName} ${kebab} list --where.${pk.name}.equalTo <value> --orderBy ${pk.name.replace(/([A-Z])/g, '_$1').toUpperCase()}_ASC`],
432435
},
433-
...(skillSpecialGroups.some((g) => g.category === 'search' || g.category === 'embedding')
434-
? [{
435-
description: `Search ${singularName} records`,
436-
code: [`${toolName} ${kebab} search "query text" --limit 10 --fields id,searchScore`],
437-
}]
438-
: []),
436+
...buildSearchExamples(skillSpecialGroups, toolName, kebab),
439437
{
440438
description: `Create a ${singularName}`,
441439
code: [
@@ -763,6 +761,7 @@ export function generateMultiTargetReadme(
763761
}
764762
const mtSpecialGroups = categorizeSpecialFields(table, registry);
765763
lines.push(...buildSpecialFieldsMarkdown(mtSpecialGroups));
764+
lines.push(...buildSearchExamplesMarkdown(mtSpecialGroups, toolName, `${tgt.name}:${kebab}`));
766765
lines.push('');
767766
}
768767

@@ -1096,12 +1095,7 @@ export function generateMultiTargetSkills(
10961095
description: `List ${singularName} records with filtering and ordering`,
10971096
code: [`${toolName} ${cmd} list --where.${pk.name}.equalTo <value> --orderBy ${pk.name.replace(/([A-Z])/g, '_$1').toUpperCase()}_ASC`],
10981097
},
1099-
...(mtSkillSpecialGroups.some((g) => g.category === 'search' || g.category === 'embedding')
1100-
? [{
1101-
description: `Search ${singularName} records`,
1102-
code: [`${toolName} ${cmd} search "query text" --limit 10 --fields id,searchScore`],
1103-
}]
1104-
: []),
1098+
...buildSearchExamples(mtSkillSpecialGroups, toolName, cmd),
11051099
{
11061100
description: `Create a ${singularName}`,
11071101
code: [

graphql/codegen/src/core/codegen/docs-utils.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,145 @@ export function buildSpecialFieldsPlain(groups: SpecialFieldGroup[]): string[] {
296296
return lines;
297297
}
298298

299+
// ---------------------------------------------------------------------------
300+
// Search-specific CLI examples for generated docs
301+
// ---------------------------------------------------------------------------
302+
303+
export interface SearchExample {
304+
description: string;
305+
code: string[];
306+
}
307+
308+
/**
309+
* Build concrete, field-specific CLI examples for tables with search fields.
310+
* Uses the same field-name derivation logic as buildSearchHandler in
311+
* table-command-generator.ts so the examples match the actual generated code.
312+
*
313+
* Returns an empty array when the table has no search/embedding fields.
314+
*/
315+
export function buildSearchExamples(
316+
specialGroups: SpecialFieldGroup[],
317+
toolName: string,
318+
cmd: string,
319+
): SearchExample[] {
320+
const examples: SearchExample[] = [];
321+
const scoreFields: string[] = [];
322+
323+
for (const group of specialGroups) {
324+
for (const field of group.fields) {
325+
// tsvector (FullText scalar) — where input uses the column name directly
326+
if (field.type.gqlType === 'FullText' && !field.type.isArray) {
327+
examples.push({
328+
description: `Full-text search via tsvector (\`${field.name}\`)`,
329+
code: [
330+
`${toolName} ${cmd} list --where.${field.name} "search query" --fields title,tsvRank`,
331+
],
332+
});
333+
scoreFields.push('tsvRank');
334+
}
335+
336+
// BM25 computed score — bodyBm25Score → bm25Body
337+
if (/Bm25Score$/.test(field.name)) {
338+
const baseName = field.name.replace(/Bm25Score$/, '');
339+
const inputName = `bm25${baseName.charAt(0).toUpperCase()}${baseName.slice(1)}`;
340+
examples.push({
341+
description: `BM25 keyword search via \`${inputName}\``,
342+
code: [
343+
`${toolName} ${cmd} list --where.${inputName}.query "search query" --fields title,${field.name}`,
344+
],
345+
});
346+
scoreFields.push(field.name);
347+
}
348+
349+
// Trigram similarity — titleTrgmSimilarity → trgmTitle
350+
if (/TrgmSimilarity$/.test(field.name)) {
351+
const baseName = field.name.replace(/TrgmSimilarity$/, '');
352+
const inputName = `trgm${baseName.charAt(0).toUpperCase()}${baseName.slice(1)}`;
353+
examples.push({
354+
description: `Fuzzy search via trigram similarity (\`${inputName}\`)`,
355+
code: [
356+
`${toolName} ${cmd} list --where.${inputName}.value "approximate query" --where.${inputName}.threshold 0.3 --fields title,${field.name}`,
357+
],
358+
});
359+
scoreFields.push(field.name);
360+
}
361+
362+
// pgvector embedding — uses column name, note about CLI limitation
363+
if (group.category === 'embedding') {
364+
examples.push({
365+
description: `Vector similarity search via \`${field.name}\` (requires JSON array)`,
366+
code: [
367+
`# Note: vector arrays must be passed as JSON strings via dot-notation`,
368+
`${toolName} ${cmd} list --where.${field.name}.vector '[0.1,0.2,0.3]' --where.${field.name}.distance 1.0 --fields title,${field.name}VectorDistance`,
369+
],
370+
});
371+
}
372+
373+
// searchScore — composite blend field, useful for ordering
374+
if (field.name === 'searchScore') {
375+
scoreFields.push('searchScore');
376+
}
377+
}
378+
}
379+
380+
// Composite fullTextSearch example (dispatches to all text adapters)
381+
const hasTextSearch = specialGroups.some(
382+
(g) => g.category === 'search' && g.fields.some(
383+
(f) => f.type.gqlType === 'FullText' || /TrgmSimilarity$/.test(f.name) || /Bm25Score$/.test(f.name),
384+
),
385+
);
386+
if (hasTextSearch) {
387+
const fieldsArg = scoreFields.length > 0
388+
? `title,${[...new Set(scoreFields)].join(',')}`
389+
: 'title';
390+
examples.push({
391+
description: 'Composite search (fullTextSearch dispatches to all text adapters)',
392+
code: [
393+
`${toolName} ${cmd} list --where.fullTextSearch "search query" --fields ${fieldsArg}`,
394+
],
395+
});
396+
}
397+
398+
// Combined search + pagination + ordering example
399+
if (examples.length > 0) {
400+
examples.push({
401+
description: 'Search with pagination and field projection',
402+
code: [
403+
`${toolName} ${cmd} list --where.fullTextSearch "query" --limit 10 --fields id,title,searchScore`,
404+
`${toolName} ${cmd} search "query" --limit 10 --fields id,title,searchScore`,
405+
],
406+
});
407+
}
408+
409+
return examples;
410+
}
411+
412+
/**
413+
* Build markdown lines for search-specific examples in README-style docs.
414+
* Returns empty array when there are no search examples.
415+
*/
416+
export function buildSearchExamplesMarkdown(
417+
specialGroups: SpecialFieldGroup[],
418+
toolName: string,
419+
cmd: string,
420+
): string[] {
421+
const examples = buildSearchExamples(specialGroups, toolName, cmd);
422+
if (examples.length === 0) return [];
423+
const lines: string[] = [];
424+
lines.push('**Search Examples:**');
425+
lines.push('');
426+
for (const ex of examples) {
427+
lines.push(`*${ex.description}:*`);
428+
lines.push('```bash');
429+
for (const c of ex.code) {
430+
lines.push(c);
431+
}
432+
lines.push('```');
433+
lines.push('');
434+
}
435+
return lines;
436+
}
437+
299438
/**
300439
* Represents a flattened argument for docs/skills generation.
301440
* INPUT_OBJECT args are expanded to dot-notation fields.

0 commit comments

Comments
 (0)