@constructive-io/graphql-codegen 4.24.5 → 4.26.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.
- package/core/codegen/cli/docs-generator.d.ts +2 -4
- package/core/codegen/cli/docs-generator.js +74 -472
- package/core/codegen/cli/index.d.ts +2 -2
- package/core/codegen/cli/index.js +1 -3
- package/core/codegen/cli/table-command-generator.js +165 -16
- package/core/codegen/docs-utils.d.ts +0 -6
- package/core/codegen/docs-utils.js +13 -7
- package/core/codegen/hooks-docs-generator.d.ts +1 -2
- package/core/codegen/hooks-docs-generator.js +0 -113
- package/core/codegen/orm/docs-generator.d.ts +1 -2
- package/core/codegen/orm/docs-generator.js +0 -126
- package/core/codegen/target-docs-generator.d.ts +1 -2
- package/core/codegen/target-docs-generator.js +0 -13
- package/core/codegen/templates/cli-utils.ts +117 -0
- package/core/codegen/utils.d.ts +2 -2
- package/core/codegen/utils.js +2 -2
- package/core/generate.js +0 -26
- package/esm/core/codegen/cli/docs-generator.d.ts +2 -4
- package/esm/core/codegen/cli/docs-generator.js +75 -471
- package/esm/core/codegen/cli/index.d.ts +2 -2
- package/esm/core/codegen/cli/index.js +1 -1
- package/esm/core/codegen/cli/table-command-generator.js +166 -17
- package/esm/core/codegen/docs-utils.d.ts +0 -6
- package/esm/core/codegen/docs-utils.js +13 -7
- package/esm/core/codegen/hooks-docs-generator.d.ts +1 -2
- package/esm/core/codegen/hooks-docs-generator.js +2 -114
- package/esm/core/codegen/orm/docs-generator.d.ts +1 -2
- package/esm/core/codegen/orm/docs-generator.js +1 -126
- package/esm/core/codegen/target-docs-generator.d.ts +1 -2
- package/esm/core/codegen/target-docs-generator.js +0 -12
- package/esm/core/codegen/utils.d.ts +2 -2
- package/esm/core/codegen/utils.js +2 -2
- package/esm/core/generate.js +4 -30
- package/esm/types/config.d.ts +1 -8
- package/package.json +16 -16
- package/types/config.d.ts +1 -8
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
2
|
import { toKebabCase } from 'komoji';
|
|
3
3
|
import { generateCode } from '../babel-ast';
|
|
4
|
-
import { getGeneratedFileHeader, getPrimaryKeyInfo, getScalarFields, getSelectableScalarFields, getTableNames, getWritableFieldNames, resolveInnerInputType, ucFirst, lcFirst, getCreateInputTypeName, getPatchTypeName, } from '../utils';
|
|
4
|
+
import { getGeneratedFileHeader, getPrimaryKeyInfo, getScalarFields, getSelectableScalarFields, getTableNames, getWritableFieldNames, resolveInnerInputType, ucFirst, lcFirst, toPascalCase, getCreateInputTypeName, getPatchTypeName, } from '../utils';
|
|
5
|
+
import { categorizeSpecialFields } from '../docs-utils';
|
|
5
6
|
function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false) {
|
|
6
7
|
const specifiers = namedImports.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
|
|
7
8
|
const decl = t.importDeclaration(specifiers, t.stringLiteral(moduleSpecifier));
|
|
@@ -153,7 +154,7 @@ function buildArgvType() {
|
|
|
153
154
|
}
|
|
154
155
|
function buildSubcommandSwitch(subcommands, handlerPrefix, usageVarName) {
|
|
155
156
|
const cases = subcommands.map((sub) => t.switchCase(t.stringLiteral(sub), [
|
|
156
|
-
t.returnStatement(t.callExpression(t.identifier(`${handlerPrefix}${
|
|
157
|
+
t.returnStatement(t.callExpression(t.identifier(`${handlerPrefix}${toPascalCase(sub)}`), [
|
|
157
158
|
t.identifier('argv'),
|
|
158
159
|
t.identifier('prompter'),
|
|
159
160
|
])),
|
|
@@ -166,17 +167,27 @@ function buildSubcommandSwitch(subcommands, handlerPrefix, usageVarName) {
|
|
|
166
167
|
}
|
|
167
168
|
function buildListHandler(table, targetName, typeRegistry) {
|
|
168
169
|
const { singularName } = getTableNames(table);
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
170
|
+
const defaultSelectObj = buildSelectObject(table, typeRegistry);
|
|
171
|
+
// --- Build the try body ---
|
|
172
|
+
const tryBody = [];
|
|
173
|
+
// const defaultSelect = { id: true, name: true, ... };
|
|
174
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
175
|
+
t.variableDeclarator(t.identifier('defaultSelect'), defaultSelectObj),
|
|
176
|
+
]));
|
|
177
|
+
// const findManyArgs = parseFindManyArgs(argv, defaultSelect);
|
|
178
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
179
|
+
t.variableDeclarator(t.identifier('findManyArgs'), t.callExpression(t.identifier('parseFindManyArgs'), [
|
|
180
|
+
t.identifier('argv'),
|
|
181
|
+
t.identifier('defaultSelect'),
|
|
182
|
+
])),
|
|
183
|
+
]));
|
|
184
|
+
tryBody.push(buildGetClientStatement(targetName));
|
|
185
|
+
// const result = await client.<singular>.findMany(findManyArgs).execute();
|
|
186
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
187
|
+
t.variableDeclarator(t.identifier('result'), t.awaitExpression(t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.memberExpression(t.identifier('client'), t.identifier(singularName)), t.identifier('findMany')), [t.identifier('findManyArgs')]), t.identifier('execute')), []))),
|
|
188
|
+
]));
|
|
189
|
+
tryBody.push(buildJsonLog(t.identifier('result')));
|
|
190
|
+
const argvParam = t.identifier('argv');
|
|
180
191
|
argvParam.typeAnnotation = buildArgvType();
|
|
181
192
|
const prompterParam = t.identifier('_prompter');
|
|
182
193
|
prompterParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Inquirerer')));
|
|
@@ -184,6 +195,129 @@ function buildListHandler(table, targetName, typeRegistry) {
|
|
|
184
195
|
t.tryStatement(t.blockStatement(tryBody), buildErrorCatch('Failed to list records.')),
|
|
185
196
|
]), false, true);
|
|
186
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Build a `handleFindFirst` function — CLI equivalent of the TS SDK's findFirst().
|
|
200
|
+
* Accepts --fields, --where.<field>.<op>, --condition.<field>.<op> flags.
|
|
201
|
+
* Internally calls findMany with first:1 and returns a single record (or null).
|
|
202
|
+
*/
|
|
203
|
+
function buildFindFirstHandler(table, targetName, typeRegistry) {
|
|
204
|
+
const { singularName } = getTableNames(table);
|
|
205
|
+
const defaultSelectObj = buildSelectObject(table, typeRegistry);
|
|
206
|
+
const tryBody = [];
|
|
207
|
+
// const defaultSelect = { ... };
|
|
208
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
209
|
+
t.variableDeclarator(t.identifier('defaultSelect'), defaultSelectObj),
|
|
210
|
+
]));
|
|
211
|
+
// const findFirstArgs = parseFindFirstArgs(argv, defaultSelect);
|
|
212
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
213
|
+
t.variableDeclarator(t.identifier('findFirstArgs'), t.callExpression(t.identifier('parseFindFirstArgs'), [
|
|
214
|
+
t.identifier('argv'),
|
|
215
|
+
t.identifier('defaultSelect'),
|
|
216
|
+
])),
|
|
217
|
+
]));
|
|
218
|
+
tryBody.push(buildGetClientStatement(targetName));
|
|
219
|
+
// const result = await client.<singular>.findFirst(findFirstArgs).execute();
|
|
220
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
221
|
+
t.variableDeclarator(t.identifier('result'), t.awaitExpression(t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.memberExpression(t.identifier('client'), t.identifier(singularName)), t.identifier('findFirst')), [t.identifier('findFirstArgs')]), t.identifier('execute')), []))),
|
|
222
|
+
]));
|
|
223
|
+
tryBody.push(buildJsonLog(t.identifier('result')));
|
|
224
|
+
const argvParam = t.identifier('argv');
|
|
225
|
+
argvParam.typeAnnotation = buildArgvType();
|
|
226
|
+
const prompterParam = t.identifier('_prompter');
|
|
227
|
+
prompterParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Inquirerer')));
|
|
228
|
+
return t.functionDeclaration(t.identifier('handleFindFirst'), [argvParam, prompterParam], t.blockStatement([
|
|
229
|
+
t.tryStatement(t.blockStatement(tryBody), buildErrorCatch('Failed to find record.')),
|
|
230
|
+
]), false, true);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Build a `handleSearch` function for tables with search-capable fields.
|
|
234
|
+
* Extracts the first positional arg as the query string and auto-builds a
|
|
235
|
+
* `where` clause that targets all detected search fields (tsvector, BM25,
|
|
236
|
+
* trigram, vector embedding). Supports --limit, --offset, --fields, --orderBy.
|
|
237
|
+
*/
|
|
238
|
+
function buildSearchHandler(table, specialGroups, targetName, typeRegistry) {
|
|
239
|
+
const { singularName } = getTableNames(table);
|
|
240
|
+
const defaultSelectObj = buildSelectObject(table, typeRegistry);
|
|
241
|
+
const tryBody = [];
|
|
242
|
+
// const query = Array.isArray(argv._) && argv._.length > 0 ? String(argv._[0]) : undefined;
|
|
243
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
244
|
+
t.variableDeclarator(t.identifier('query'), t.conditionalExpression(t.logicalExpression('&&', t.callExpression(t.memberExpression(t.identifier('Array'), t.identifier('isArray')), [t.memberExpression(t.identifier('argv'), t.identifier('_'))]), t.binaryExpression('>', t.memberExpression(t.memberExpression(t.identifier('argv'), t.identifier('_')), t.identifier('length')), t.numericLiteral(0))), t.callExpression(t.identifier('String'), [
|
|
245
|
+
t.memberExpression(t.memberExpression(t.identifier('argv'), t.identifier('_')), t.numericLiteral(0), true),
|
|
246
|
+
]), t.identifier('undefined'))),
|
|
247
|
+
]));
|
|
248
|
+
// if (!query) { console.error('Usage: ... search <query>'); process.exit(1); }
|
|
249
|
+
tryBody.push(t.ifStatement(t.unaryExpression('!', t.identifier('query')), t.blockStatement([
|
|
250
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [t.stringLiteral('Error: search requires a <query> argument')])),
|
|
251
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
|
|
252
|
+
])));
|
|
253
|
+
// Build the where clause properties from detected search fields
|
|
254
|
+
// e.g. { tsvContent: { query }, bm25Body: { query }, trgmTitle: { value: query, threshold: 0.3 }, vectorEmbedding: { value: query } }
|
|
255
|
+
const whereProps = [];
|
|
256
|
+
for (const group of specialGroups) {
|
|
257
|
+
for (const field of group.fields) {
|
|
258
|
+
if (field.type.gqlType === 'FullText' && !field.type.isArray) {
|
|
259
|
+
// tsvector field: { query }
|
|
260
|
+
whereProps.push(t.objectProperty(t.identifier(field.name), t.objectExpression([
|
|
261
|
+
t.objectProperty(t.identifier('query'), t.identifier('query'), false, true),
|
|
262
|
+
])));
|
|
263
|
+
}
|
|
264
|
+
else if (/Bm25Score$/.test(field.name)) {
|
|
265
|
+
// BM25 computed score field — derive the input field name:
|
|
266
|
+
// bodyBm25Score -> bm25Body (strip trailing "Bm25Score", prefix with "bm25")
|
|
267
|
+
const baseName = field.name.replace(/Bm25Score$/, '');
|
|
268
|
+
const inputFieldName = `bm25${baseName.charAt(0).toUpperCase()}${baseName.slice(1)}`;
|
|
269
|
+
whereProps.push(t.objectProperty(t.identifier(inputFieldName), t.objectExpression([
|
|
270
|
+
t.objectProperty(t.identifier('query'), t.identifier('query'), false, true),
|
|
271
|
+
])));
|
|
272
|
+
}
|
|
273
|
+
else if (/TrgmSimilarity$/.test(field.name)) {
|
|
274
|
+
// Trigram computed score field — derive the input field name:
|
|
275
|
+
// titleTrgmSimilarity -> trgmTitle
|
|
276
|
+
const baseName = field.name.replace(/TrgmSimilarity$/, '');
|
|
277
|
+
const inputFieldName = `trgm${baseName.charAt(0).toUpperCase()}${baseName.slice(1)}`;
|
|
278
|
+
whereProps.push(t.objectProperty(t.identifier(inputFieldName), t.objectExpression([
|
|
279
|
+
t.objectProperty(t.identifier('value'), t.identifier('query')),
|
|
280
|
+
t.objectProperty(t.identifier('threshold'), t.numericLiteral(0.3)),
|
|
281
|
+
])));
|
|
282
|
+
}
|
|
283
|
+
else if (group.category === 'embedding') {
|
|
284
|
+
// Vector embedding field: { value: query }
|
|
285
|
+
whereProps.push(t.objectProperty(t.identifier(field.name), t.objectExpression([
|
|
286
|
+
t.objectProperty(t.identifier('value'), t.identifier('query')),
|
|
287
|
+
])));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// const searchWhere = { tsvContent: { query }, ... };
|
|
292
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
293
|
+
t.variableDeclarator(t.identifier('searchWhere'), t.objectExpression(whereProps)),
|
|
294
|
+
]));
|
|
295
|
+
// const defaultSelect = { ... };
|
|
296
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
297
|
+
t.variableDeclarator(t.identifier('defaultSelect'), defaultSelectObj),
|
|
298
|
+
]));
|
|
299
|
+
// const findManyArgs = parseFindManyArgs(argv, defaultSelect, searchWhere);
|
|
300
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
301
|
+
t.variableDeclarator(t.identifier('findManyArgs'), t.callExpression(t.identifier('parseFindManyArgs'), [
|
|
302
|
+
t.identifier('argv'),
|
|
303
|
+
t.identifier('defaultSelect'),
|
|
304
|
+
t.identifier('searchWhere'),
|
|
305
|
+
])),
|
|
306
|
+
]));
|
|
307
|
+
tryBody.push(buildGetClientStatement(targetName));
|
|
308
|
+
// const result = await client.<singular>.findMany(findManyArgs).execute();
|
|
309
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
310
|
+
t.variableDeclarator(t.identifier('result'), t.awaitExpression(t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.memberExpression(t.identifier('client'), t.identifier(singularName)), t.identifier('findMany')), [t.identifier('findManyArgs')]), t.identifier('execute')), []))),
|
|
311
|
+
]));
|
|
312
|
+
tryBody.push(buildJsonLog(t.identifier('result')));
|
|
313
|
+
const argvParam = t.identifier('argv');
|
|
314
|
+
argvParam.typeAnnotation = buildArgvType();
|
|
315
|
+
const prompterParam = t.identifier('_prompter');
|
|
316
|
+
prompterParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Inquirerer')));
|
|
317
|
+
return t.functionDeclaration(t.identifier('handleSearch'), [argvParam, prompterParam], t.blockStatement([
|
|
318
|
+
t.tryStatement(t.blockStatement(tryBody), buildErrorCatch('Failed to search records.')),
|
|
319
|
+
]), false, true);
|
|
320
|
+
}
|
|
187
321
|
function buildGetHandler(table, targetName, typeRegistry) {
|
|
188
322
|
const { singularName } = getTableNames(table);
|
|
189
323
|
const pkFields = getPrimaryKeyInfo(table);
|
|
@@ -394,7 +528,7 @@ export function generateTableCommand(table, options) {
|
|
|
394
528
|
]));
|
|
395
529
|
statements.push(createImportDeclaration(executorPath, ['getClient']));
|
|
396
530
|
const utilsPath = options?.targetName ? '../../utils' : '../utils';
|
|
397
|
-
statements.push(createImportDeclaration(utilsPath, ['coerceAnswers', 'stripUndefined']));
|
|
531
|
+
statements.push(createImportDeclaration(utilsPath, ['coerceAnswers', 'parseFindFirstArgs', 'parseFindManyArgs', 'stripUndefined']));
|
|
398
532
|
statements.push(createImportDeclaration(utilsPath, ['FieldSchema'], true));
|
|
399
533
|
// Import ORM input types for proper type assertions in mutation handlers.
|
|
400
534
|
// These types ensure that cleanedData is cast to the correct ORM input type
|
|
@@ -426,7 +560,12 @@ export function generateTableCommand(table, options) {
|
|
|
426
560
|
const hasUpdate = table.query?.update !== undefined && table.query?.update !== null;
|
|
427
561
|
const hasDelete = table.query?.delete !== undefined && table.query?.delete !== null;
|
|
428
562
|
const hasGet = table.query?.one !== null || hasUpdate || hasDelete;
|
|
429
|
-
|
|
563
|
+
// Detect whether this table has search-capable fields (tsvector, BM25, trgm, vector embedding)
|
|
564
|
+
const specialGroups = categorizeSpecialFields(table, options?.typeRegistry);
|
|
565
|
+
const hasSearchFields = specialGroups.some((g) => g.category === 'search' || g.category === 'embedding');
|
|
566
|
+
const subcommands = ['list', 'find-first'];
|
|
567
|
+
if (hasSearchFields)
|
|
568
|
+
subcommands.push('search');
|
|
430
569
|
if (hasGet)
|
|
431
570
|
subcommands.push('get');
|
|
432
571
|
subcommands.push('create');
|
|
@@ -439,8 +578,11 @@ export function generateTableCommand(table, options) {
|
|
|
439
578
|
`${commandName} <command>`,
|
|
440
579
|
'',
|
|
441
580
|
'Commands:',
|
|
442
|
-
` list List
|
|
581
|
+
` list List ${singularName} records`,
|
|
582
|
+
` find-first Find first matching ${singularName} record`,
|
|
443
583
|
];
|
|
584
|
+
if (hasSearchFields)
|
|
585
|
+
usageLines.push(` search <query> Search ${singularName} records`);
|
|
444
586
|
if (hasGet)
|
|
445
587
|
usageLines.push(` get Get a ${singularName} by ID`);
|
|
446
588
|
usageLines.push(` create Create a new ${singularName}`);
|
|
@@ -448,7 +590,11 @@ export function generateTableCommand(table, options) {
|
|
|
448
590
|
usageLines.push(` update Update an existing ${singularName}`);
|
|
449
591
|
if (hasDelete)
|
|
450
592
|
usageLines.push(` delete Delete a ${singularName}`);
|
|
451
|
-
usageLines.push('', ' --
|
|
593
|
+
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)', '');
|
|
594
|
+
if (hasSearchFields) {
|
|
595
|
+
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', '');
|
|
596
|
+
}
|
|
597
|
+
usageLines.push(' --help, -h Show this help message', '');
|
|
452
598
|
statements.push(t.variableDeclaration('const', [
|
|
453
599
|
t.variableDeclarator(t.identifier('usage'), t.stringLiteral(usageLines.join('\n'))),
|
|
454
600
|
]));
|
|
@@ -510,6 +656,9 @@ export function generateTableCommand(table, options) {
|
|
|
510
656
|
const tn = options?.targetName;
|
|
511
657
|
const ormTypes = { createInputTypeName, patchTypeName, innerFieldName };
|
|
512
658
|
statements.push(buildListHandler(table, tn, options?.typeRegistry));
|
|
659
|
+
statements.push(buildFindFirstHandler(table, tn, options?.typeRegistry));
|
|
660
|
+
if (hasSearchFields)
|
|
661
|
+
statements.push(buildSearchHandler(table, specialGroups, tn, options?.typeRegistry));
|
|
513
662
|
if (hasGet)
|
|
514
663
|
statements.push(buildGetHandler(table, tn, options?.typeRegistry));
|
|
515
664
|
statements.push(buildMutationHandler(table, 'create', tn, options?.typeRegistry, ormTypes));
|
|
@@ -4,12 +4,6 @@ export interface GeneratedDocFile {
|
|
|
4
4
|
fileName: string;
|
|
5
5
|
content: string;
|
|
6
6
|
}
|
|
7
|
-
export interface McpTool {
|
|
8
|
-
name: string;
|
|
9
|
-
description: string;
|
|
10
|
-
inputSchema: Record<string, unknown>;
|
|
11
|
-
_meta?: Record<string, unknown>;
|
|
12
|
-
}
|
|
13
7
|
export interface SkillDefinition {
|
|
14
8
|
name: string;
|
|
15
9
|
description: string;
|
|
@@ -29,18 +29,17 @@ export function getReadmeFooter() {
|
|
|
29
29
|
}
|
|
30
30
|
export function resolveDocsConfig(docs) {
|
|
31
31
|
if (docs === true) {
|
|
32
|
-
return { readme: true, agents: true,
|
|
32
|
+
return { readme: true, agents: true, skills: true };
|
|
33
33
|
}
|
|
34
34
|
if (docs === false) {
|
|
35
|
-
return { readme: false, agents: false,
|
|
35
|
+
return { readme: false, agents: false, skills: false };
|
|
36
36
|
}
|
|
37
37
|
if (!docs) {
|
|
38
|
-
return { readme: true, agents: true,
|
|
38
|
+
return { readme: true, agents: true, skills: false };
|
|
39
39
|
}
|
|
40
40
|
return {
|
|
41
41
|
readme: docs.readme ?? true,
|
|
42
42
|
agents: docs.agents ?? true,
|
|
43
|
-
mcp: docs.mcp ?? false,
|
|
44
43
|
skills: docs.skills ?? false,
|
|
45
44
|
};
|
|
46
45
|
}
|
|
@@ -97,16 +96,23 @@ function isPostGISField(f) {
|
|
|
97
96
|
return false;
|
|
98
97
|
}
|
|
99
98
|
function isEmbeddingField(f) {
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
// VectorCodecPlugin maps pgvector `vector` columns to the `Vector` GQL scalar.
|
|
100
|
+
// This is the primary detection path — no _meta or pgType enrichment needed.
|
|
101
|
+
if (f.type.gqlType === 'Vector')
|
|
102
102
|
return true;
|
|
103
|
+
// Legacy fallback: name-based heuristic for schemas without VectorCodecPlugin
|
|
103
104
|
if (/embedding$/i.test(f.name) && f.type.isArray && f.type.gqlType === 'Float')
|
|
104
105
|
return true;
|
|
105
106
|
return false;
|
|
106
107
|
}
|
|
107
108
|
function isTsvectorField(f) {
|
|
108
109
|
const pgType = f.type.pgType?.toLowerCase();
|
|
109
|
-
|
|
110
|
+
if (pgType === 'tsvector')
|
|
111
|
+
return true;
|
|
112
|
+
// Fallback: PostGraphile maps tsvector columns to the FullText GQL scalar
|
|
113
|
+
if (f.type.gqlType === 'FullText' && !f.type.isArray)
|
|
114
|
+
return true;
|
|
115
|
+
return false;
|
|
110
116
|
}
|
|
111
117
|
function isSearchComputedField(f) {
|
|
112
118
|
if (f.name === 'searchScore')
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Operation, Table, TypeRegistry } from '../../types/schema';
|
|
2
|
-
import type { GeneratedDocFile
|
|
2
|
+
import type { GeneratedDocFile } from './docs-utils';
|
|
3
3
|
export declare function generateHooksReadme(tables: Table[], customOperations: Operation[], registry?: TypeRegistry): GeneratedDocFile;
|
|
4
4
|
export declare function generateHooksAgentsDocs(tables: Table[], customOperations: Operation[]): GeneratedDocFile;
|
|
5
|
-
export declare function getHooksMcpTools(tables: Table[], customOperations: Operation[]): McpTool[];
|
|
6
5
|
export declare function generateHooksSkills(tables: Table[], customOperations: Operation[], targetName: string, registry?: TypeRegistry): GeneratedDocFile[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toKebabCase } from 'komoji';
|
|
2
|
-
import { buildSkillFile, buildSkillReference, formatArgType, fieldPlaceholder, pkPlaceholder, argPlaceholder, getReadmeHeader, getReadmeFooter,
|
|
3
|
-
import { getTableNames, getScalarFields, getPrimaryKeyInfo, getListQueryHookName, getSingleQueryHookName, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, hasValidPrimaryKey, ucFirst,
|
|
2
|
+
import { buildSkillFile, buildSkillReference, formatArgType, fieldPlaceholder, pkPlaceholder, argPlaceholder, getReadmeHeader, getReadmeFooter, } from './docs-utils';
|
|
3
|
+
import { getTableNames, getScalarFields, getPrimaryKeyInfo, getListQueryHookName, getSingleQueryHookName, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, hasValidPrimaryKey, ucFirst, } from './utils';
|
|
4
4
|
function getCustomHookName(op) {
|
|
5
5
|
if (op.kind === 'query') {
|
|
6
6
|
return `use${ucFirst(op.name)}Query`;
|
|
@@ -161,118 +161,6 @@ export function generateHooksAgentsDocs(tables, customOperations) {
|
|
|
161
161
|
content: lines.join('\n'),
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
|
-
export function getHooksMcpTools(tables, customOperations) {
|
|
165
|
-
const tools = [];
|
|
166
|
-
for (const table of tables) {
|
|
167
|
-
const { singularName, pluralName } = getTableNames(table);
|
|
168
|
-
const pk = getPrimaryKeyInfo(table)[0];
|
|
169
|
-
const scalarFields = getScalarFields(table);
|
|
170
|
-
tools.push({
|
|
171
|
-
name: `hooks_${lcFirst(pluralName)}_query`,
|
|
172
|
-
description: table.description || `React Query hook to list all ${pluralName}`,
|
|
173
|
-
inputSchema: {
|
|
174
|
-
type: 'object',
|
|
175
|
-
properties: {
|
|
176
|
-
fields: {
|
|
177
|
-
type: 'object',
|
|
178
|
-
description: `Fields to select: { ${scalarFields.map((f) => f.name).join(', ')} }`,
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
if (hasValidPrimaryKey(table)) {
|
|
184
|
-
tools.push({
|
|
185
|
-
name: `hooks_${lcFirst(singularName)}_query`,
|
|
186
|
-
description: table.description || `React Query hook to get a single ${singularName} by ${pk.name}`,
|
|
187
|
-
inputSchema: {
|
|
188
|
-
type: 'object',
|
|
189
|
-
properties: {
|
|
190
|
-
[pk.name]: {
|
|
191
|
-
type: gqlTypeToJsonSchemaType(pk.gqlType),
|
|
192
|
-
description: `${table.name} ${pk.name}`,
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
required: [pk.name],
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
tools.push({
|
|
200
|
-
name: `hooks_create_${lcFirst(singularName)}_mutation`,
|
|
201
|
-
description: table.description || `React Query mutation hook to create a ${singularName}`,
|
|
202
|
-
inputSchema: {
|
|
203
|
-
type: 'object',
|
|
204
|
-
properties: Object.fromEntries(scalarFields
|
|
205
|
-
.filter((f) => f.name !== pk.name &&
|
|
206
|
-
f.name !== 'nodeId' &&
|
|
207
|
-
f.name !== 'createdAt' &&
|
|
208
|
-
f.name !== 'updatedAt')
|
|
209
|
-
.map((f) => [
|
|
210
|
-
f.name,
|
|
211
|
-
{
|
|
212
|
-
type: gqlTypeToJsonSchemaType(f.type.gqlType),
|
|
213
|
-
description: `${table.name} ${f.name}`,
|
|
214
|
-
},
|
|
215
|
-
])),
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
if (hasValidPrimaryKey(table)) {
|
|
219
|
-
tools.push({
|
|
220
|
-
name: `hooks_update_${lcFirst(singularName)}_mutation`,
|
|
221
|
-
description: table.description || `React Query mutation hook to update a ${singularName}`,
|
|
222
|
-
inputSchema: {
|
|
223
|
-
type: 'object',
|
|
224
|
-
properties: {
|
|
225
|
-
[pk.name]: {
|
|
226
|
-
type: gqlTypeToJsonSchemaType(pk.gqlType),
|
|
227
|
-
description: `${table.name} ${pk.name}`,
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
required: [pk.name],
|
|
231
|
-
},
|
|
232
|
-
});
|
|
233
|
-
tools.push({
|
|
234
|
-
name: `hooks_delete_${lcFirst(singularName)}_mutation`,
|
|
235
|
-
description: table.description || `React Query mutation hook to delete a ${singularName}`,
|
|
236
|
-
inputSchema: {
|
|
237
|
-
type: 'object',
|
|
238
|
-
properties: {
|
|
239
|
-
[pk.name]: {
|
|
240
|
-
type: gqlTypeToJsonSchemaType(pk.gqlType),
|
|
241
|
-
description: `${table.name} ${pk.name}`,
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
required: [pk.name],
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
for (const op of customOperations) {
|
|
250
|
-
const hookName = getCustomHookName(op);
|
|
251
|
-
const props = {};
|
|
252
|
-
const required = [];
|
|
253
|
-
for (const arg of op.args) {
|
|
254
|
-
const isRequired = arg.type.kind === 'NON_NULL';
|
|
255
|
-
const baseType = isRequired && arg.type.ofType ? arg.type.ofType : arg.type;
|
|
256
|
-
props[arg.name] = {
|
|
257
|
-
type: gqlTypeToJsonSchemaType(baseType.name ?? 'String'),
|
|
258
|
-
description: arg.description || arg.name,
|
|
259
|
-
};
|
|
260
|
-
if (isRequired) {
|
|
261
|
-
required.push(arg.name);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
tools.push({
|
|
265
|
-
name: `hooks_${hookName}`,
|
|
266
|
-
description: op.description || `${ucFirst(op.kind)} hook for ${op.name}`,
|
|
267
|
-
inputSchema: {
|
|
268
|
-
type: 'object',
|
|
269
|
-
properties: props,
|
|
270
|
-
...(required.length > 0 ? { required } : {}),
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
return tools;
|
|
275
|
-
}
|
|
276
164
|
export function generateHooksSkills(tables, customOperations, targetName, registry) {
|
|
277
165
|
const files = [];
|
|
278
166
|
const skillName = `hooks-${targetName}`;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Operation, Table, TypeRegistry } from '../../../types/schema';
|
|
2
|
-
import type { GeneratedDocFile
|
|
2
|
+
import type { GeneratedDocFile } from '../docs-utils';
|
|
3
3
|
export declare function generateOrmReadme(tables: Table[], customOperations: Operation[], registry?: TypeRegistry): GeneratedDocFile;
|
|
4
4
|
export declare function generateOrmAgentsDocs(tables: Table[], customOperations: Operation[]): GeneratedDocFile;
|
|
5
|
-
export declare function getOrmMcpTools(tables: Table[], customOperations: Operation[]): McpTool[];
|
|
6
5
|
export declare function generateOrmSkills(tables: Table[], customOperations: Operation[], targetName: string, registry?: TypeRegistry): GeneratedDocFile[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toKebabCase } from 'komoji';
|
|
2
|
-
import { buildSkillFile, buildSkillReference, formatArgType, fieldPlaceholder, pkPlaceholder, argPlaceholder, getEditableFields, categorizeSpecialFields, buildSpecialFieldsMarkdown, getReadmeHeader, getReadmeFooter,
|
|
2
|
+
import { buildSkillFile, buildSkillReference, formatArgType, fieldPlaceholder, pkPlaceholder, argPlaceholder, getEditableFields, categorizeSpecialFields, buildSpecialFieldsMarkdown, getReadmeHeader, getReadmeFooter, } from '../docs-utils';
|
|
3
3
|
import { getScalarFields, getTableNames, getPrimaryKeyInfo, lcFirst, } from '../utils';
|
|
4
4
|
export function generateOrmReadme(tables, customOperations, registry) {
|
|
5
5
|
const lines = [];
|
|
@@ -154,131 +154,6 @@ export function generateOrmAgentsDocs(tables, customOperations) {
|
|
|
154
154
|
content: lines.join('\n'),
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
-
export function getOrmMcpTools(tables, customOperations) {
|
|
158
|
-
const tools = [];
|
|
159
|
-
for (const table of tables) {
|
|
160
|
-
const { singularName } = getTableNames(table);
|
|
161
|
-
const pk = getPrimaryKeyInfo(table)[0];
|
|
162
|
-
const scalarFields = getScalarFields(table);
|
|
163
|
-
const editableFields = getEditableFields(table);
|
|
164
|
-
tools.push({
|
|
165
|
-
name: `orm_${lcFirst(singularName)}_findMany`,
|
|
166
|
-
description: table.description || `List all ${table.name} records via ORM`,
|
|
167
|
-
inputSchema: {
|
|
168
|
-
type: 'object',
|
|
169
|
-
properties: {
|
|
170
|
-
first: {
|
|
171
|
-
type: 'integer',
|
|
172
|
-
description: 'Limit number of results',
|
|
173
|
-
},
|
|
174
|
-
offset: {
|
|
175
|
-
type: 'integer',
|
|
176
|
-
description: 'Offset for pagination',
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
tools.push({
|
|
182
|
-
name: `orm_${lcFirst(singularName)}_findOne`,
|
|
183
|
-
description: table.description || `Get a single ${table.name} record by ${pk.name}`,
|
|
184
|
-
inputSchema: {
|
|
185
|
-
type: 'object',
|
|
186
|
-
properties: {
|
|
187
|
-
[pk.name]: {
|
|
188
|
-
type: gqlTypeToJsonSchemaType(pk.gqlType),
|
|
189
|
-
description: `${table.name} ${pk.name}`,
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
required: [pk.name],
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
const createProps = {};
|
|
196
|
-
for (const f of editableFields) {
|
|
197
|
-
createProps[f.name] = {
|
|
198
|
-
type: gqlTypeToJsonSchemaType(f.type.gqlType),
|
|
199
|
-
description: `${table.name} ${f.name}`,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
tools.push({
|
|
203
|
-
name: `orm_${lcFirst(singularName)}_create`,
|
|
204
|
-
description: table.description || `Create a new ${table.name} record`,
|
|
205
|
-
inputSchema: {
|
|
206
|
-
type: 'object',
|
|
207
|
-
properties: createProps,
|
|
208
|
-
required: editableFields.map((f) => f.name),
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
const updateProps = {
|
|
212
|
-
[pk.name]: {
|
|
213
|
-
type: gqlTypeToJsonSchemaType(pk.gqlType),
|
|
214
|
-
description: `${table.name} ${pk.name}`,
|
|
215
|
-
},
|
|
216
|
-
};
|
|
217
|
-
for (const f of editableFields) {
|
|
218
|
-
updateProps[f.name] = {
|
|
219
|
-
type: gqlTypeToJsonSchemaType(f.type.gqlType),
|
|
220
|
-
description: `${table.name} ${f.name}`,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
tools.push({
|
|
224
|
-
name: `orm_${lcFirst(singularName)}_update`,
|
|
225
|
-
description: table.description || `Update an existing ${table.name} record`,
|
|
226
|
-
inputSchema: {
|
|
227
|
-
type: 'object',
|
|
228
|
-
properties: updateProps,
|
|
229
|
-
required: [pk.name],
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
tools.push({
|
|
233
|
-
name: `orm_${lcFirst(singularName)}_delete`,
|
|
234
|
-
description: table.description || `Delete a ${table.name} record by ${pk.name}`,
|
|
235
|
-
inputSchema: {
|
|
236
|
-
type: 'object',
|
|
237
|
-
properties: {
|
|
238
|
-
[pk.name]: {
|
|
239
|
-
type: gqlTypeToJsonSchemaType(pk.gqlType),
|
|
240
|
-
description: `${table.name} ${pk.name}`,
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
required: [pk.name],
|
|
244
|
-
},
|
|
245
|
-
_meta: {
|
|
246
|
-
fields: scalarFields.map((f) => ({
|
|
247
|
-
name: f.name,
|
|
248
|
-
type: f.type.gqlType,
|
|
249
|
-
editable: editableFields.some((ef) => ef.name === f.name),
|
|
250
|
-
primaryKey: f.name === pk.name,
|
|
251
|
-
})),
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
for (const op of customOperations) {
|
|
256
|
-
const accessor = op.kind === 'query' ? 'query' : 'mutation';
|
|
257
|
-
const props = {};
|
|
258
|
-
const required = [];
|
|
259
|
-
for (const arg of op.args) {
|
|
260
|
-
const isRequired = arg.type.kind === 'NON_NULL';
|
|
261
|
-
const baseType = isRequired && arg.type.ofType ? arg.type.ofType : arg.type;
|
|
262
|
-
props[arg.name] = {
|
|
263
|
-
type: gqlTypeToJsonSchemaType(baseType.name ?? 'String'),
|
|
264
|
-
description: arg.description || arg.name,
|
|
265
|
-
};
|
|
266
|
-
if (isRequired) {
|
|
267
|
-
required.push(arg.name);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
tools.push({
|
|
271
|
-
name: `orm_${accessor}_${op.name}`,
|
|
272
|
-
description: op.description || `Execute ${op.name} ${op.kind}`,
|
|
273
|
-
inputSchema: {
|
|
274
|
-
type: 'object',
|
|
275
|
-
properties: props,
|
|
276
|
-
...(required.length > 0 ? { required } : {}),
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
return tools;
|
|
281
|
-
}
|
|
282
157
|
export function generateOrmSkills(tables, customOperations, targetName, registry) {
|
|
283
158
|
const files = [];
|
|
284
159
|
const skillName = `orm-${targetName}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GraphQLSDKConfigTarget } from '../../types/config';
|
|
2
|
-
import type { GeneratedDocFile
|
|
2
|
+
import type { GeneratedDocFile } from './docs-utils';
|
|
3
3
|
export interface TargetReadmeOptions {
|
|
4
4
|
hasOrm: boolean;
|
|
5
5
|
hasHooks: boolean;
|
|
@@ -10,7 +10,6 @@ export interface TargetReadmeOptions {
|
|
|
10
10
|
config: GraphQLSDKConfigTarget;
|
|
11
11
|
}
|
|
12
12
|
export declare function generateTargetReadme(options: TargetReadmeOptions): GeneratedDocFile;
|
|
13
|
-
export declare function generateCombinedMcpConfig(tools: McpTool[], name: string): GeneratedDocFile;
|
|
14
13
|
export interface RootRootReadmeTarget {
|
|
15
14
|
name: string;
|
|
16
15
|
output: string;
|
|
@@ -72,18 +72,6 @@ export function generateTargetReadme(options) {
|
|
|
72
72
|
content: lines.join('\n'),
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
export function generateCombinedMcpConfig(tools, name) {
|
|
76
|
-
const mcpConfig = {
|
|
77
|
-
name,
|
|
78
|
-
version: '1.0.0',
|
|
79
|
-
description: `MCP tool definitions for ${name} SDK (auto-generated from GraphQL schema)`,
|
|
80
|
-
tools,
|
|
81
|
-
};
|
|
82
|
-
return {
|
|
83
|
-
fileName: 'mcp.json',
|
|
84
|
-
content: JSON.stringify(mcpConfig, null, 2) + '\n',
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
75
|
export function generateRootRootReadme(targets) {
|
|
88
76
|
const lines = [];
|
|
89
77
|
lines.push(...getReadmeHeader('GraphQL SDK'));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Codegen utilities - naming conventions, type mapping, and helpers
|
|
3
3
|
*/
|
|
4
|
-
import { lcFirst, toCamelCase,
|
|
4
|
+
import { lcFirst, toCamelCase, toConstantCase, toPascalCase, ucFirst } from 'inflekt';
|
|
5
5
|
import type { Field, FieldType, Table, TypeRegistry } from '../../types/schema';
|
|
6
|
-
export { lcFirst, toCamelCase, toPascalCase,
|
|
6
|
+
export { lcFirst, toCamelCase, toPascalCase, toConstantCase, ucFirst };
|
|
7
7
|
export interface TableNames {
|
|
8
8
|
/** PascalCase singular (e.g., "Car") */
|
|
9
9
|
typeName: string;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Codegen utilities - naming conventions, type mapping, and helpers
|
|
3
3
|
*/
|
|
4
|
-
import { lcFirst, pluralize, toCamelCase,
|
|
4
|
+
import { lcFirst, pluralize, toCamelCase, toConstantCase, toPascalCase, ucFirst, } from 'inflekt';
|
|
5
5
|
import { scalarToFilterType, scalarToTsType } from './scalars';
|
|
6
6
|
// Re-export string manipulation helpers from inflekt (single source of truth)
|
|
7
|
-
export { lcFirst, toCamelCase, toPascalCase,
|
|
7
|
+
export { lcFirst, toCamelCase, toPascalCase, toConstantCase, ucFirst };
|
|
8
8
|
/**
|
|
9
9
|
* Derive all naming variants from a table
|
|
10
10
|
*/
|