@constructive-io/graphql-codegen 4.14.4 → 4.15.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 +1 -1
- package/core/codegen/cli/docs-generator.js +66 -471
- package/core/codegen/docs-utils.d.ts +36 -1
- package/core/codegen/docs-utils.js +148 -2
- package/core/codegen/hooks-docs-generator.js +23 -122
- package/core/codegen/orm/docs-generator.js +29 -85
- package/esm/core/codegen/cli/docs-generator.d.ts +1 -1
- package/esm/core/codegen/cli/docs-generator.js +67 -472
- package/esm/core/codegen/docs-utils.d.ts +36 -1
- package/esm/core/codegen/docs-utils.js +145 -3
- package/esm/core/codegen/hooks-docs-generator.js +24 -123
- package/esm/core/codegen/orm/docs-generator.js +31 -87
- package/package.json +10 -10
|
@@ -35,7 +35,42 @@ export declare function getReadmeFooter(): string[];
|
|
|
35
35
|
export declare function resolveDocsConfig(docs: DocsConfig | boolean | undefined): DocsConfig;
|
|
36
36
|
export declare function formatArgType(arg: CleanOperation['args'][number]): string;
|
|
37
37
|
export declare function formatTypeRef(t: CleanOperation['args'][number]['type']): string;
|
|
38
|
-
export declare function getEditableFields(table: CleanTable): CleanField[];
|
|
38
|
+
export declare function getEditableFields(table: CleanTable, typeRegistry?: TypeRegistry): CleanField[];
|
|
39
|
+
/**
|
|
40
|
+
* Identify search/computed fields on a table — fields present in the GraphQL
|
|
41
|
+
* type but NOT in the create input type. These are plugin-added fields like
|
|
42
|
+
* trgm similarity scores, tsvector ranks, searchScore, etc.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getSearchFields(table: CleanTable, typeRegistry?: TypeRegistry): CleanField[];
|
|
45
|
+
export interface SpecialFieldGroup {
|
|
46
|
+
/** Category key */
|
|
47
|
+
category: 'geospatial' | 'embedding' | 'search';
|
|
48
|
+
/** Human-readable label */
|
|
49
|
+
label: string;
|
|
50
|
+
/** One-line description of this category */
|
|
51
|
+
description: string;
|
|
52
|
+
/** Fields belonging to this category */
|
|
53
|
+
fields: CleanField[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Categorize "special" fields on a table into PostGIS, pgvector, and
|
|
57
|
+
* Unified Search groups. Returns only non-empty groups.
|
|
58
|
+
*
|
|
59
|
+
* The function inspects ALL scalar fields (not just computed ones) so that
|
|
60
|
+
* real columns (geometry, vector, tsvector) are also surfaced with
|
|
61
|
+
* descriptive context in generated docs.
|
|
62
|
+
*/
|
|
63
|
+
export declare function categorizeSpecialFields(table: CleanTable, typeRegistry?: TypeRegistry): SpecialFieldGroup[];
|
|
64
|
+
/**
|
|
65
|
+
* Build markdown lines describing special fields for README-style docs.
|
|
66
|
+
* Returns empty array when there are no special fields.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildSpecialFieldsMarkdown(groups: SpecialFieldGroup[]): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Build plain-text lines describing special fields for AGENTS-style docs.
|
|
71
|
+
* Returns empty array when there are no special fields.
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildSpecialFieldsPlain(groups: SpecialFieldGroup[]): string[];
|
|
39
74
|
/**
|
|
40
75
|
* Represents a flattened argument for docs/skills generation.
|
|
41
76
|
* INPUT_OBJECT args are expanded to dot-notation fields.
|
|
@@ -6,6 +6,10 @@ exports.resolveDocsConfig = resolveDocsConfig;
|
|
|
6
6
|
exports.formatArgType = formatArgType;
|
|
7
7
|
exports.formatTypeRef = formatTypeRef;
|
|
8
8
|
exports.getEditableFields = getEditableFields;
|
|
9
|
+
exports.getSearchFields = getSearchFields;
|
|
10
|
+
exports.categorizeSpecialFields = categorizeSpecialFields;
|
|
11
|
+
exports.buildSpecialFieldsMarkdown = buildSpecialFieldsMarkdown;
|
|
12
|
+
exports.buildSpecialFieldsPlain = buildSpecialFieldsPlain;
|
|
9
13
|
exports.cleanTypeName = cleanTypeName;
|
|
10
14
|
exports.flattenArgs = flattenArgs;
|
|
11
15
|
exports.flattenedArgsToFlags = flattenedArgsToFlags;
|
|
@@ -74,12 +78,154 @@ function formatTypeRef(t) {
|
|
|
74
78
|
}
|
|
75
79
|
return t.name ?? 'unknown';
|
|
76
80
|
}
|
|
77
|
-
function getEditableFields(table) {
|
|
81
|
+
function getEditableFields(table, typeRegistry) {
|
|
78
82
|
const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
|
|
83
|
+
const writableFields = (0, utils_1.getWritableFieldNames)(table, typeRegistry);
|
|
79
84
|
return (0, utils_1.getScalarFields)(table).filter((f) => f.name !== pk.name &&
|
|
80
85
|
f.name !== 'nodeId' &&
|
|
81
86
|
f.name !== 'createdAt' &&
|
|
82
|
-
f.name !== 'updatedAt'
|
|
87
|
+
f.name !== 'updatedAt' &&
|
|
88
|
+
// When a TypeRegistry is available, filter out computed/plugin-added
|
|
89
|
+
// fields (e.g. search scores, trgm similarity) that aren't real columns
|
|
90
|
+
(writableFields === null || writableFields.has(f.name)));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Identify search/computed fields on a table — fields present in the GraphQL
|
|
94
|
+
* type but NOT in the create input type. These are plugin-added fields like
|
|
95
|
+
* trgm similarity scores, tsvector ranks, searchScore, etc.
|
|
96
|
+
*/
|
|
97
|
+
function getSearchFields(table, typeRegistry) {
|
|
98
|
+
const writableFields = (0, utils_1.getWritableFieldNames)(table, typeRegistry);
|
|
99
|
+
if (writableFields === null)
|
|
100
|
+
return [];
|
|
101
|
+
const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
|
|
102
|
+
return (0, utils_1.getScalarFields)(table).filter((f) => f.name !== pk.name &&
|
|
103
|
+
f.name !== 'nodeId' &&
|
|
104
|
+
f.name !== 'createdAt' &&
|
|
105
|
+
f.name !== 'updatedAt' &&
|
|
106
|
+
!writableFields.has(f.name));
|
|
107
|
+
}
|
|
108
|
+
function isPostGISField(f) {
|
|
109
|
+
const pgType = f.type.pgType?.toLowerCase();
|
|
110
|
+
if (pgType === 'geometry' || pgType === 'geography')
|
|
111
|
+
return true;
|
|
112
|
+
const gql = f.type.gqlType;
|
|
113
|
+
if (/^(GeoJSON|GeographyPoint|GeographyLineString|GeographyPolygon|GeometryPoint|GeometryLineString|GeometryPolygon|GeographyMulti|GeometryMulti|GeometryCollection|GeographyCollection)/i.test(gql))
|
|
114
|
+
return true;
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
function isEmbeddingField(f) {
|
|
118
|
+
const pgType = f.type.pgType?.toLowerCase();
|
|
119
|
+
if (pgType === 'vector')
|
|
120
|
+
return true;
|
|
121
|
+
if (/embedding$/i.test(f.name) && f.type.isArray && f.type.gqlType === 'Float')
|
|
122
|
+
return true;
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
function isTsvectorField(f) {
|
|
126
|
+
const pgType = f.type.pgType?.toLowerCase();
|
|
127
|
+
return pgType === 'tsvector';
|
|
128
|
+
}
|
|
129
|
+
function isSearchComputedField(f) {
|
|
130
|
+
if (f.name === 'searchScore')
|
|
131
|
+
return true;
|
|
132
|
+
if (/TrgmSimilarity$/.test(f.name))
|
|
133
|
+
return true;
|
|
134
|
+
if (/TsvectorRank$/.test(f.name))
|
|
135
|
+
return true;
|
|
136
|
+
if (/Bm25Score$/.test(f.name))
|
|
137
|
+
return true;
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Categorize "special" fields on a table into PostGIS, pgvector, and
|
|
142
|
+
* Unified Search groups. Returns only non-empty groups.
|
|
143
|
+
*
|
|
144
|
+
* The function inspects ALL scalar fields (not just computed ones) so that
|
|
145
|
+
* real columns (geometry, vector, tsvector) are also surfaced with
|
|
146
|
+
* descriptive context in generated docs.
|
|
147
|
+
*/
|
|
148
|
+
function categorizeSpecialFields(table, typeRegistry) {
|
|
149
|
+
const allFields = (0, utils_1.getScalarFields)(table);
|
|
150
|
+
const computedFields = getSearchFields(table, typeRegistry);
|
|
151
|
+
const computedSet = new Set(computedFields.map((f) => f.name));
|
|
152
|
+
const geospatial = [];
|
|
153
|
+
const embedding = [];
|
|
154
|
+
const search = [];
|
|
155
|
+
for (const f of allFields) {
|
|
156
|
+
if (isPostGISField(f)) {
|
|
157
|
+
geospatial.push(f);
|
|
158
|
+
}
|
|
159
|
+
else if (isEmbeddingField(f)) {
|
|
160
|
+
embedding.push(f);
|
|
161
|
+
}
|
|
162
|
+
else if (isTsvectorField(f)) {
|
|
163
|
+
search.push(f);
|
|
164
|
+
}
|
|
165
|
+
else if (computedSet.has(f.name) && isSearchComputedField(f)) {
|
|
166
|
+
search.push(f);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const groups = [];
|
|
170
|
+
if (geospatial.length > 0) {
|
|
171
|
+
groups.push({
|
|
172
|
+
category: 'geospatial',
|
|
173
|
+
label: 'PostGIS geospatial fields',
|
|
174
|
+
description: 'Geographic/geometric columns managed by PostGIS. Supports spatial queries (distance, containment, intersection) via the Unified Search API PostGIS adapter.',
|
|
175
|
+
fields: geospatial,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (embedding.length > 0) {
|
|
179
|
+
groups.push({
|
|
180
|
+
category: 'embedding',
|
|
181
|
+
label: 'pgvector embedding fields',
|
|
182
|
+
description: 'High-dimensional vector columns for semantic similarity search. Query via the Unified Search API pgvector adapter using cosine, L2, or inner-product distance.',
|
|
183
|
+
fields: embedding,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (search.length > 0) {
|
|
187
|
+
groups.push({
|
|
188
|
+
category: 'search',
|
|
189
|
+
label: 'Unified Search API fields',
|
|
190
|
+
description: 'Fields provided by the Unified Search plugin. Includes full-text search (tsvector/BM25), trigram similarity scores, and the combined searchScore. Computed fields are read-only and cannot be set in create/update operations.',
|
|
191
|
+
fields: search,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return groups;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Build markdown lines describing special fields for README-style docs.
|
|
198
|
+
* Returns empty array when there are no special fields.
|
|
199
|
+
*/
|
|
200
|
+
function buildSpecialFieldsMarkdown(groups) {
|
|
201
|
+
if (groups.length === 0)
|
|
202
|
+
return [];
|
|
203
|
+
const lines = [];
|
|
204
|
+
for (const g of groups) {
|
|
205
|
+
const fieldList = g.fields.map((f) => `\`${f.name}\``).join(', ');
|
|
206
|
+
lines.push(`> **${g.label}:** ${fieldList}`);
|
|
207
|
+
lines.push(`> ${g.description}`);
|
|
208
|
+
lines.push('');
|
|
209
|
+
}
|
|
210
|
+
return lines;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Build plain-text lines describing special fields for AGENTS-style docs.
|
|
214
|
+
* Returns empty array when there are no special fields.
|
|
215
|
+
*/
|
|
216
|
+
function buildSpecialFieldsPlain(groups) {
|
|
217
|
+
if (groups.length === 0)
|
|
218
|
+
return [];
|
|
219
|
+
const lines = [];
|
|
220
|
+
lines.push('SPECIAL FIELDS:');
|
|
221
|
+
for (const g of groups) {
|
|
222
|
+
lines.push(` [${g.label}]`);
|
|
223
|
+
lines.push(` ${g.description}`);
|
|
224
|
+
for (const f of g.fields) {
|
|
225
|
+
lines.push(` ${f.name}: ${cleanTypeName(f.type.gqlType)}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return lines;
|
|
83
229
|
}
|
|
84
230
|
function unwrapNonNull(typeRef) {
|
|
85
231
|
if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
|
|
@@ -123,17 +123,19 @@ function generateHooksReadme(tables, customOperations) {
|
|
|
123
123
|
}
|
|
124
124
|
function generateHooksAgentsDocs(tables, customOperations) {
|
|
125
125
|
const lines = [];
|
|
126
|
-
|
|
126
|
+
const tableCount = tables.length;
|
|
127
|
+
const customOpCount = customOperations.length;
|
|
128
|
+
lines.push('# React Query Hooks');
|
|
127
129
|
lines.push('');
|
|
128
130
|
lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
|
|
129
|
-
lines.push('> This document is structured for LLM/agent consumption.');
|
|
130
131
|
lines.push('');
|
|
131
|
-
lines.push('##
|
|
132
|
+
lines.push('## Stack');
|
|
132
133
|
lines.push('');
|
|
133
|
-
lines.push('React Query hooks wrapping ORM operations
|
|
134
|
-
lines.push('
|
|
134
|
+
lines.push('- React Query hooks wrapping ORM operations (TypeScript)');
|
|
135
|
+
lines.push(`- ${tableCount} table${tableCount !== 1 ? 's' : ''}${customOpCount > 0 ? `, ${customOpCount} custom operation${customOpCount !== 1 ? 's' : ''}` : ''}`);
|
|
136
|
+
lines.push('- Query hooks return `UseQueryResult`, mutation hooks return `UseMutationResult`');
|
|
135
137
|
lines.push('');
|
|
136
|
-
lines.push('##
|
|
138
|
+
lines.push('## Quick Start');
|
|
137
139
|
lines.push('');
|
|
138
140
|
lines.push('```typescript');
|
|
139
141
|
lines.push("import { configure } from './hooks';");
|
|
@@ -144,123 +146,22 @@ function generateHooksAgentsDocs(tables, customOperations) {
|
|
|
144
146
|
lines.push('// Wrap app in <QueryClientProvider client={queryClient}>');
|
|
145
147
|
lines.push('```');
|
|
146
148
|
lines.push('');
|
|
147
|
-
lines.push('##
|
|
149
|
+
lines.push('## Resources');
|
|
150
|
+
lines.push('');
|
|
151
|
+
lines.push(`- **Full API reference:** [README.md](./README.md) — hook docs for all ${tableCount} tables`);
|
|
152
|
+
lines.push('- **Schema types:** [types.ts](./types.ts)');
|
|
153
|
+
lines.push('- **Hooks module:** [hooks.ts](./hooks.ts)');
|
|
154
|
+
lines.push('');
|
|
155
|
+
lines.push('## Conventions');
|
|
156
|
+
lines.push('');
|
|
157
|
+
lines.push('- Query hooks: `use<PluralName>Query`, `use<SingularName>Query`');
|
|
158
|
+
lines.push('- Mutation hooks: `useCreate<Name>Mutation`, `useUpdate<Name>Mutation`, `useDelete<Name>Mutation`');
|
|
159
|
+
lines.push('- All hooks accept a `selection` parameter to pick fields');
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push('## Boundaries');
|
|
162
|
+
lines.push('');
|
|
163
|
+
lines.push('All files in this directory are generated. Do not edit manually.');
|
|
148
164
|
lines.push('');
|
|
149
|
-
for (const table of tables) {
|
|
150
|
-
const { singularName, pluralName } = (0, utils_1.getTableNames)(table);
|
|
151
|
-
const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
|
|
152
|
-
const scalarFields = (0, utils_1.getScalarFields)(table);
|
|
153
|
-
lines.push(`### HOOK: ${(0, utils_1.getListQueryHookName)(table)}`);
|
|
154
|
-
lines.push('');
|
|
155
|
-
lines.push(`${table.description || `List all ${pluralName}`}.`);
|
|
156
|
-
lines.push('');
|
|
157
|
-
lines.push('```');
|
|
158
|
-
lines.push(`TYPE: query`);
|
|
159
|
-
lines.push(`USAGE: ${(0, utils_1.getListQueryHookName)(table)}({ selection: { fields: { ... } } })`);
|
|
160
|
-
lines.push('');
|
|
161
|
-
lines.push('INPUT:');
|
|
162
|
-
lines.push(' selection: { fields: Record<string, boolean> } - Fields to select');
|
|
163
|
-
lines.push('');
|
|
164
|
-
lines.push('OUTPUT: UseQueryResult<Array<{');
|
|
165
|
-
for (const f of scalarFields) {
|
|
166
|
-
lines.push(` ${f.name}: ${(0, utils_1.fieldTypeToTs)(f.type)}`);
|
|
167
|
-
}
|
|
168
|
-
lines.push('}>>');
|
|
169
|
-
lines.push('```');
|
|
170
|
-
lines.push('');
|
|
171
|
-
if ((0, utils_1.hasValidPrimaryKey)(table)) {
|
|
172
|
-
lines.push(`### HOOK: ${(0, utils_1.getSingleQueryHookName)(table)}`);
|
|
173
|
-
lines.push('');
|
|
174
|
-
lines.push(`${table.description || `Get a single ${singularName} by ${pk.name}`}.`);
|
|
175
|
-
lines.push('');
|
|
176
|
-
lines.push('```');
|
|
177
|
-
lines.push(`TYPE: query`);
|
|
178
|
-
lines.push(`USAGE: ${(0, utils_1.getSingleQueryHookName)(table)}({ ${pk.name}: '<value>', selection: { fields: { ... } } })`);
|
|
179
|
-
lines.push('');
|
|
180
|
-
lines.push('INPUT:');
|
|
181
|
-
lines.push(` ${pk.name}: ${pk.tsType} (required)`);
|
|
182
|
-
lines.push(' selection: { fields: Record<string, boolean> } - Fields to select');
|
|
183
|
-
lines.push('');
|
|
184
|
-
lines.push('OUTPUT: UseQueryResult<{');
|
|
185
|
-
for (const f of scalarFields) {
|
|
186
|
-
lines.push(` ${f.name}: ${(0, utils_1.fieldTypeToTs)(f.type)}`);
|
|
187
|
-
}
|
|
188
|
-
lines.push('}>');
|
|
189
|
-
lines.push('```');
|
|
190
|
-
lines.push('');
|
|
191
|
-
}
|
|
192
|
-
lines.push(`### HOOK: ${(0, utils_1.getCreateMutationHookName)(table)}`);
|
|
193
|
-
lines.push('');
|
|
194
|
-
lines.push(`${table.description || `Create a new ${singularName}`}.`);
|
|
195
|
-
lines.push('');
|
|
196
|
-
lines.push('```');
|
|
197
|
-
lines.push('TYPE: mutation');
|
|
198
|
-
lines.push(`USAGE: const { mutate } = ${(0, utils_1.getCreateMutationHookName)(table)}({ selection: { fields: { ... } } })`);
|
|
199
|
-
lines.push('');
|
|
200
|
-
lines.push('OUTPUT: UseMutationResult');
|
|
201
|
-
lines.push('```');
|
|
202
|
-
lines.push('');
|
|
203
|
-
if ((0, utils_1.hasValidPrimaryKey)(table)) {
|
|
204
|
-
lines.push(`### HOOK: ${(0, utils_1.getUpdateMutationHookName)(table)}`);
|
|
205
|
-
lines.push('');
|
|
206
|
-
lines.push(`${table.description || `Update an existing ${singularName}`}.`);
|
|
207
|
-
lines.push('');
|
|
208
|
-
lines.push('```');
|
|
209
|
-
lines.push('TYPE: mutation');
|
|
210
|
-
lines.push(`USAGE: const { mutate } = ${(0, utils_1.getUpdateMutationHookName)(table)}({ selection: { fields: { ... } } })`);
|
|
211
|
-
lines.push('');
|
|
212
|
-
lines.push('OUTPUT: UseMutationResult');
|
|
213
|
-
lines.push('```');
|
|
214
|
-
lines.push('');
|
|
215
|
-
lines.push(`### HOOK: ${(0, utils_1.getDeleteMutationHookName)(table)}`);
|
|
216
|
-
lines.push('');
|
|
217
|
-
lines.push(`${table.description || `Delete a ${singularName}`}.`);
|
|
218
|
-
lines.push('');
|
|
219
|
-
lines.push('```');
|
|
220
|
-
lines.push('TYPE: mutation');
|
|
221
|
-
lines.push(`USAGE: const { mutate } = ${(0, utils_1.getDeleteMutationHookName)(table)}({})`);
|
|
222
|
-
lines.push('');
|
|
223
|
-
lines.push('OUTPUT: UseMutationResult');
|
|
224
|
-
lines.push('```');
|
|
225
|
-
lines.push('');
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (customOperations.length > 0) {
|
|
229
|
-
lines.push('## CUSTOM OPERATION HOOKS');
|
|
230
|
-
lines.push('');
|
|
231
|
-
for (const op of customOperations) {
|
|
232
|
-
const hookName = getCustomHookName(op);
|
|
233
|
-
lines.push(`### HOOK: ${hookName}`);
|
|
234
|
-
lines.push('');
|
|
235
|
-
lines.push(op.description || op.name);
|
|
236
|
-
lines.push('');
|
|
237
|
-
lines.push('```');
|
|
238
|
-
lines.push(`TYPE: ${op.kind}`);
|
|
239
|
-
if (op.args.length > 0) {
|
|
240
|
-
if (op.kind === 'mutation') {
|
|
241
|
-
lines.push(`USAGE: const { mutate } = ${hookName}()`);
|
|
242
|
-
lines.push(` mutate({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} })`);
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
lines.push(`USAGE: ${hookName}({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} })`);
|
|
246
|
-
}
|
|
247
|
-
lines.push('');
|
|
248
|
-
lines.push('INPUT:');
|
|
249
|
-
for (const arg of op.args) {
|
|
250
|
-
lines.push(` ${arg.name}: ${(0, docs_utils_1.formatArgType)(arg)}`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
lines.push(`USAGE: ${hookName}()`);
|
|
255
|
-
lines.push('');
|
|
256
|
-
lines.push('INPUT: none');
|
|
257
|
-
}
|
|
258
|
-
lines.push('');
|
|
259
|
-
lines.push(`OUTPUT: ${op.kind === 'query' ? 'UseQueryResult' : 'UseMutationResult'}`);
|
|
260
|
-
lines.push('```');
|
|
261
|
-
lines.push('');
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
165
|
return {
|
|
265
166
|
fileName: 'AGENTS.md',
|
|
266
167
|
content: lines.join('\n'),
|
|
@@ -70,6 +70,8 @@ function generateOrmReadme(tables, customOperations) {
|
|
|
70
70
|
lines.push(`const deleted = await db.${singularName}.delete({ where: { ${pk.name}: '<value>' } }).execute();`);
|
|
71
71
|
lines.push('```');
|
|
72
72
|
lines.push('');
|
|
73
|
+
const ormSpecialGroups = (0, docs_utils_1.categorizeSpecialFields)(table);
|
|
74
|
+
lines.push(...(0, docs_utils_1.buildSpecialFieldsMarkdown)(ormSpecialGroups));
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
if (customOperations.length > 0) {
|
|
@@ -113,17 +115,19 @@ function generateOrmReadme(tables, customOperations) {
|
|
|
113
115
|
}
|
|
114
116
|
function generateOrmAgentsDocs(tables, customOperations) {
|
|
115
117
|
const lines = [];
|
|
116
|
-
|
|
118
|
+
const tableCount = tables.length;
|
|
119
|
+
const customOpCount = customOperations.length;
|
|
120
|
+
lines.push('# ORM Client');
|
|
117
121
|
lines.push('');
|
|
118
122
|
lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
|
|
119
|
-
lines.push('> This document is structured for LLM/agent consumption.');
|
|
120
123
|
lines.push('');
|
|
121
|
-
lines.push('##
|
|
124
|
+
lines.push('## Stack');
|
|
122
125
|
lines.push('');
|
|
123
|
-
lines.push('Prisma-like ORM client for
|
|
124
|
-
lines.push('
|
|
126
|
+
lines.push('- Prisma-like ORM client for a GraphQL API (TypeScript)');
|
|
127
|
+
lines.push(`- ${tableCount} model${tableCount !== 1 ? 's' : ''}${customOpCount > 0 ? `, ${customOpCount} custom operation${customOpCount !== 1 ? 's' : ''}` : ''}`);
|
|
128
|
+
lines.push('- All methods return a query builder; call `.execute()` to run');
|
|
125
129
|
lines.push('');
|
|
126
|
-
lines.push('##
|
|
130
|
+
lines.push('## Quick Start');
|
|
127
131
|
lines.push('');
|
|
128
132
|
lines.push('```typescript');
|
|
129
133
|
lines.push("import { createClient } from './orm';");
|
|
@@ -134,88 +138,22 @@ function generateOrmAgentsDocs(tables, customOperations) {
|
|
|
134
138
|
lines.push('});');
|
|
135
139
|
lines.push('```');
|
|
136
140
|
lines.push('');
|
|
137
|
-
lines.push('##
|
|
141
|
+
lines.push('## Resources');
|
|
138
142
|
lines.push('');
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const scalarFields = (0, utils_1.getScalarFields)(table);
|
|
143
|
-
const editableFields = (0, docs_utils_1.getEditableFields)(table);
|
|
144
|
-
lines.push(`### MODEL: ${singularName}`);
|
|
145
|
-
lines.push('');
|
|
146
|
-
lines.push(`Access: \`db.${singularName}\``);
|
|
147
|
-
lines.push('');
|
|
148
|
-
lines.push('```');
|
|
149
|
-
lines.push('METHODS:');
|
|
150
|
-
lines.push(` db.${singularName}.findMany({ select, where?, orderBy?, first?, offset? })`);
|
|
151
|
-
lines.push(` db.${singularName}.findOne({ ${pk.name}, select })`);
|
|
152
|
-
lines.push(` db.${singularName}.create({ data: { ${editableFields.map((f) => f.name).join(', ')} }, select })`);
|
|
153
|
-
lines.push(` db.${singularName}.update({ where: { ${pk.name} }, data, select })`);
|
|
154
|
-
lines.push(` db.${singularName}.delete({ where: { ${pk.name} } })`);
|
|
155
|
-
lines.push('');
|
|
156
|
-
lines.push('FIELDS:');
|
|
157
|
-
for (const f of scalarFields) {
|
|
158
|
-
const isPk = f.name === pk.name;
|
|
159
|
-
lines.push(` ${f.name}: ${(0, utils_1.fieldTypeToTs)(f.type)}${isPk ? ' (primary key)' : ''}`);
|
|
160
|
-
}
|
|
161
|
-
lines.push('');
|
|
162
|
-
lines.push('EDITABLE FIELDS:');
|
|
163
|
-
for (const f of editableFields) {
|
|
164
|
-
lines.push(` ${f.name}: ${(0, utils_1.fieldTypeToTs)(f.type)}`);
|
|
165
|
-
}
|
|
166
|
-
lines.push('');
|
|
167
|
-
lines.push('OUTPUT: Promise<JSON>');
|
|
168
|
-
lines.push(` findMany: [{ ${scalarFields.map((f) => f.name).join(', ')} }]`);
|
|
169
|
-
lines.push(` findOne: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
170
|
-
lines.push(` create: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
171
|
-
lines.push(` update: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
172
|
-
lines.push(` delete: { ${pk.name} }`);
|
|
173
|
-
lines.push('```');
|
|
174
|
-
lines.push('');
|
|
175
|
-
}
|
|
176
|
-
if (customOperations.length > 0) {
|
|
177
|
-
lines.push('## CUSTOM OPERATIONS');
|
|
178
|
-
lines.push('');
|
|
179
|
-
for (const op of customOperations) {
|
|
180
|
-
const accessor = op.kind === 'query' ? 'query' : 'mutation';
|
|
181
|
-
lines.push(`### OPERATION: ${op.name}`);
|
|
182
|
-
lines.push('');
|
|
183
|
-
lines.push(op.description || op.name);
|
|
184
|
-
lines.push('');
|
|
185
|
-
lines.push('```');
|
|
186
|
-
lines.push(`TYPE: ${op.kind}`);
|
|
187
|
-
lines.push(`ACCESS: db.${accessor}.${op.name}`);
|
|
188
|
-
if (op.args.length > 0) {
|
|
189
|
-
lines.push(`USAGE: db.${accessor}.${op.name}({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} }).execute()`);
|
|
190
|
-
lines.push('');
|
|
191
|
-
lines.push('INPUT:');
|
|
192
|
-
for (const arg of op.args) {
|
|
193
|
-
lines.push(` ${arg.name}: ${(0, docs_utils_1.formatArgType)(arg)}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
lines.push(`USAGE: db.${accessor}.${op.name}().execute()`);
|
|
198
|
-
lines.push('');
|
|
199
|
-
lines.push('INPUT: none');
|
|
200
|
-
}
|
|
201
|
-
lines.push('');
|
|
202
|
-
lines.push('OUTPUT: Promise<JSON>');
|
|
203
|
-
lines.push('```');
|
|
204
|
-
lines.push('');
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
lines.push('## PATTERNS');
|
|
143
|
+
lines.push(`- **Full API reference:** [README.md](./README.md) — model docs for all ${tableCount} tables`);
|
|
144
|
+
lines.push('- **Schema types:** [types.ts](./types.ts)');
|
|
145
|
+
lines.push('- **ORM client:** [orm.ts](./orm.ts)');
|
|
208
146
|
lines.push('');
|
|
209
|
-
lines.push('
|
|
210
|
-
lines.push('// All methods require .execute() to run');
|
|
211
|
-
lines.push('const result = await db.modelName.findMany({ select: { id: true } }).execute();');
|
|
147
|
+
lines.push('## Conventions');
|
|
212
148
|
lines.push('');
|
|
213
|
-
lines.push('
|
|
214
|
-
lines.push('
|
|
149
|
+
lines.push('- Access models via `db.<ModelName>` (e.g. `db.User`)');
|
|
150
|
+
lines.push('- CRUD methods: `findMany`, `findOne`, `create`, `update`, `delete`');
|
|
151
|
+
lines.push('- Always call `.execute()` to run the query');
|
|
152
|
+
lines.push('- Custom operations via `db.query.<name>` or `db.mutation.<name>`');
|
|
215
153
|
lines.push('');
|
|
216
|
-
lines.push('
|
|
217
|
-
lines.push(
|
|
218
|
-
lines.push('
|
|
154
|
+
lines.push('## Boundaries');
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push('All files in this directory are generated. Do not edit manually.');
|
|
219
157
|
lines.push('');
|
|
220
158
|
return {
|
|
221
159
|
fileName: 'AGENTS.md',
|
|
@@ -359,11 +297,17 @@ function generateOrmSkills(tables, customOperations, targetName) {
|
|
|
359
297
|
const modelName = (0, utils_1.lcFirst)(singularName);
|
|
360
298
|
const refName = (0, komoji_1.toKebabCase)(singularName);
|
|
361
299
|
referenceNames.push(refName);
|
|
300
|
+
const ormSkillSpecialGroups = (0, docs_utils_1.categorizeSpecialFields)(table);
|
|
301
|
+
const ormSkillBaseDesc = table.description || `ORM operations for ${table.name} records`;
|
|
302
|
+
const ormSkillSpecialDesc = ormSkillSpecialGroups.length > 0
|
|
303
|
+
? ormSkillBaseDesc + '\n\n' +
|
|
304
|
+
ormSkillSpecialGroups.map((g) => `**${g.label}:** ${g.fields.map((f) => `\`${f.name}\``).join(', ')}\n${g.description}`).join('\n\n')
|
|
305
|
+
: ormSkillBaseDesc;
|
|
362
306
|
files.push({
|
|
363
307
|
fileName: `${skillName}/references/${refName}.md`,
|
|
364
308
|
content: (0, docs_utils_1.buildSkillReference)({
|
|
365
309
|
title: singularName,
|
|
366
|
-
description:
|
|
310
|
+
description: ormSkillSpecialDesc,
|
|
367
311
|
language: 'typescript',
|
|
368
312
|
usage: [
|
|
369
313
|
`db.${modelName}.findMany({ select: { id: true } }).execute()`,
|
|
@@ -3,7 +3,7 @@ import type { GeneratedDocFile, McpTool } from '../docs-utils';
|
|
|
3
3
|
export { resolveDocsConfig } from '../docs-utils';
|
|
4
4
|
export type { GeneratedDocFile, McpTool } from '../docs-utils';
|
|
5
5
|
export declare function generateReadme(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, registry?: TypeRegistry): GeneratedDocFile;
|
|
6
|
-
export declare function generateAgentsDocs(tables: CleanTable[], customOperations: CleanOperation[], toolName: string,
|
|
6
|
+
export declare function generateAgentsDocs(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, _registry?: TypeRegistry): GeneratedDocFile;
|
|
7
7
|
export declare function getCliMcpTools(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, registry?: TypeRegistry): McpTool[];
|
|
8
8
|
export declare function generateSkills(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, targetName: string, registry?: TypeRegistry): GeneratedDocFile[];
|
|
9
9
|
export interface MultiTargetDocsInput {
|