@constructive-io/graphql-codegen 4.14.4 → 4.15.1
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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getScalarFields, getPrimaryKeyInfo } from './utils';
|
|
1
|
+
import { getScalarFields, getPrimaryKeyInfo, getWritableFieldNames } from './utils';
|
|
2
2
|
const CONSTRUCTIVE_LOGO_URL = 'https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg';
|
|
3
3
|
const CONSTRUCTIVE_REPO = 'https://github.com/constructive-io/constructive';
|
|
4
4
|
export function getReadmeHeader(title) {
|
|
@@ -60,12 +60,154 @@ export function formatTypeRef(t) {
|
|
|
60
60
|
}
|
|
61
61
|
return t.name ?? 'unknown';
|
|
62
62
|
}
|
|
63
|
-
export function getEditableFields(table) {
|
|
63
|
+
export function getEditableFields(table, typeRegistry) {
|
|
64
64
|
const pk = getPrimaryKeyInfo(table)[0];
|
|
65
|
+
const writableFields = getWritableFieldNames(table, typeRegistry);
|
|
65
66
|
return getScalarFields(table).filter((f) => f.name !== pk.name &&
|
|
66
67
|
f.name !== 'nodeId' &&
|
|
67
68
|
f.name !== 'createdAt' &&
|
|
68
|
-
f.name !== 'updatedAt'
|
|
69
|
+
f.name !== 'updatedAt' &&
|
|
70
|
+
// When a TypeRegistry is available, filter out computed/plugin-added
|
|
71
|
+
// fields (e.g. search scores, trgm similarity) that aren't real columns
|
|
72
|
+
(writableFields === null || writableFields.has(f.name)));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Identify search/computed fields on a table — fields present in the GraphQL
|
|
76
|
+
* type but NOT in the create input type. These are plugin-added fields like
|
|
77
|
+
* trgm similarity scores, tsvector ranks, searchScore, etc.
|
|
78
|
+
*/
|
|
79
|
+
export function getSearchFields(table, typeRegistry) {
|
|
80
|
+
const writableFields = getWritableFieldNames(table, typeRegistry);
|
|
81
|
+
if (writableFields === null)
|
|
82
|
+
return [];
|
|
83
|
+
const pk = getPrimaryKeyInfo(table)[0];
|
|
84
|
+
return getScalarFields(table).filter((f) => f.name !== pk.name &&
|
|
85
|
+
f.name !== 'nodeId' &&
|
|
86
|
+
f.name !== 'createdAt' &&
|
|
87
|
+
f.name !== 'updatedAt' &&
|
|
88
|
+
!writableFields.has(f.name));
|
|
89
|
+
}
|
|
90
|
+
function isPostGISField(f) {
|
|
91
|
+
const pgType = f.type.pgType?.toLowerCase();
|
|
92
|
+
if (pgType === 'geometry' || pgType === 'geography')
|
|
93
|
+
return true;
|
|
94
|
+
const gql = f.type.gqlType;
|
|
95
|
+
if (/^(GeoJSON|GeographyPoint|GeographyLineString|GeographyPolygon|GeometryPoint|GeometryLineString|GeometryPolygon|GeographyMulti|GeometryMulti|GeometryCollection|GeographyCollection)/i.test(gql))
|
|
96
|
+
return true;
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
function isEmbeddingField(f) {
|
|
100
|
+
const pgType = f.type.pgType?.toLowerCase();
|
|
101
|
+
if (pgType === 'vector')
|
|
102
|
+
return true;
|
|
103
|
+
if (/embedding$/i.test(f.name) && f.type.isArray && f.type.gqlType === 'Float')
|
|
104
|
+
return true;
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function isTsvectorField(f) {
|
|
108
|
+
const pgType = f.type.pgType?.toLowerCase();
|
|
109
|
+
return pgType === 'tsvector';
|
|
110
|
+
}
|
|
111
|
+
function isSearchComputedField(f) {
|
|
112
|
+
if (f.name === 'searchScore')
|
|
113
|
+
return true;
|
|
114
|
+
if (/TrgmSimilarity$/.test(f.name))
|
|
115
|
+
return true;
|
|
116
|
+
if (/TsvectorRank$/.test(f.name))
|
|
117
|
+
return true;
|
|
118
|
+
if (/Bm25Score$/.test(f.name))
|
|
119
|
+
return true;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Categorize "special" fields on a table into PostGIS, pgvector, and
|
|
124
|
+
* Unified Search groups. Returns only non-empty groups.
|
|
125
|
+
*
|
|
126
|
+
* The function inspects ALL scalar fields (not just computed ones) so that
|
|
127
|
+
* real columns (geometry, vector, tsvector) are also surfaced with
|
|
128
|
+
* descriptive context in generated docs.
|
|
129
|
+
*/
|
|
130
|
+
export function categorizeSpecialFields(table, typeRegistry) {
|
|
131
|
+
const allFields = getScalarFields(table);
|
|
132
|
+
const computedFields = getSearchFields(table, typeRegistry);
|
|
133
|
+
const computedSet = new Set(computedFields.map((f) => f.name));
|
|
134
|
+
const geospatial = [];
|
|
135
|
+
const embedding = [];
|
|
136
|
+
const search = [];
|
|
137
|
+
for (const f of allFields) {
|
|
138
|
+
if (isPostGISField(f)) {
|
|
139
|
+
geospatial.push(f);
|
|
140
|
+
}
|
|
141
|
+
else if (isEmbeddingField(f)) {
|
|
142
|
+
embedding.push(f);
|
|
143
|
+
}
|
|
144
|
+
else if (isTsvectorField(f)) {
|
|
145
|
+
search.push(f);
|
|
146
|
+
}
|
|
147
|
+
else if (computedSet.has(f.name) && isSearchComputedField(f)) {
|
|
148
|
+
search.push(f);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const groups = [];
|
|
152
|
+
if (geospatial.length > 0) {
|
|
153
|
+
groups.push({
|
|
154
|
+
category: 'geospatial',
|
|
155
|
+
label: 'PostGIS geospatial fields',
|
|
156
|
+
description: 'Geographic/geometric columns managed by PostGIS. Supports spatial queries (distance, containment, intersection) via the Unified Search API PostGIS adapter.',
|
|
157
|
+
fields: geospatial,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (embedding.length > 0) {
|
|
161
|
+
groups.push({
|
|
162
|
+
category: 'embedding',
|
|
163
|
+
label: 'pgvector embedding fields',
|
|
164
|
+
description: 'High-dimensional vector columns for semantic similarity search. Query via the Unified Search API pgvector adapter using cosine, L2, or inner-product distance.',
|
|
165
|
+
fields: embedding,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (search.length > 0) {
|
|
169
|
+
groups.push({
|
|
170
|
+
category: 'search',
|
|
171
|
+
label: 'Unified Search API fields',
|
|
172
|
+
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.',
|
|
173
|
+
fields: search,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return groups;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Build markdown lines describing special fields for README-style docs.
|
|
180
|
+
* Returns empty array when there are no special fields.
|
|
181
|
+
*/
|
|
182
|
+
export function buildSpecialFieldsMarkdown(groups) {
|
|
183
|
+
if (groups.length === 0)
|
|
184
|
+
return [];
|
|
185
|
+
const lines = [];
|
|
186
|
+
for (const g of groups) {
|
|
187
|
+
const fieldList = g.fields.map((f) => `\`${f.name}\``).join(', ');
|
|
188
|
+
lines.push(`> **${g.label}:** ${fieldList}`);
|
|
189
|
+
lines.push(`> ${g.description}`);
|
|
190
|
+
lines.push('');
|
|
191
|
+
}
|
|
192
|
+
return lines;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Build plain-text lines describing special fields for AGENTS-style docs.
|
|
196
|
+
* Returns empty array when there are no special fields.
|
|
197
|
+
*/
|
|
198
|
+
export function buildSpecialFieldsPlain(groups) {
|
|
199
|
+
if (groups.length === 0)
|
|
200
|
+
return [];
|
|
201
|
+
const lines = [];
|
|
202
|
+
lines.push('SPECIAL FIELDS:');
|
|
203
|
+
for (const g of groups) {
|
|
204
|
+
lines.push(` [${g.label}]`);
|
|
205
|
+
lines.push(` ${g.description}`);
|
|
206
|
+
for (const f of g.fields) {
|
|
207
|
+
lines.push(` ${f.name}: ${cleanTypeName(f.type.gqlType)}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return lines;
|
|
69
211
|
}
|
|
70
212
|
function unwrapNonNull(typeRef) {
|
|
71
213
|
if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toKebabCase } from 'komoji';
|
|
2
2
|
import { buildSkillFile, buildSkillReference, formatArgType, getReadmeHeader, getReadmeFooter, gqlTypeToJsonSchemaType, } from './docs-utils';
|
|
3
|
-
import { getTableNames, getScalarFields, getPrimaryKeyInfo, getListQueryHookName, getSingleQueryHookName, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, hasValidPrimaryKey, ucFirst, lcFirst,
|
|
3
|
+
import { getTableNames, getScalarFields, getPrimaryKeyInfo, getListQueryHookName, getSingleQueryHookName, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, hasValidPrimaryKey, ucFirst, lcFirst, } from './utils';
|
|
4
4
|
function getCustomHookName(op) {
|
|
5
5
|
if (op.kind === 'query') {
|
|
6
6
|
return `use${ucFirst(op.name)}Query`;
|
|
@@ -117,17 +117,19 @@ export function generateHooksReadme(tables, customOperations) {
|
|
|
117
117
|
}
|
|
118
118
|
export function generateHooksAgentsDocs(tables, customOperations) {
|
|
119
119
|
const lines = [];
|
|
120
|
-
|
|
120
|
+
const tableCount = tables.length;
|
|
121
|
+
const customOpCount = customOperations.length;
|
|
122
|
+
lines.push('# React Query Hooks');
|
|
121
123
|
lines.push('');
|
|
122
124
|
lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
|
|
123
|
-
lines.push('> This document is structured for LLM/agent consumption.');
|
|
124
125
|
lines.push('');
|
|
125
|
-
lines.push('##
|
|
126
|
+
lines.push('## Stack');
|
|
126
127
|
lines.push('');
|
|
127
|
-
lines.push('React Query hooks wrapping ORM operations
|
|
128
|
-
lines.push('
|
|
128
|
+
lines.push('- React Query hooks wrapping ORM operations (TypeScript)');
|
|
129
|
+
lines.push(`- ${tableCount} table${tableCount !== 1 ? 's' : ''}${customOpCount > 0 ? `, ${customOpCount} custom operation${customOpCount !== 1 ? 's' : ''}` : ''}`);
|
|
130
|
+
lines.push('- Query hooks return `UseQueryResult`, mutation hooks return `UseMutationResult`');
|
|
129
131
|
lines.push('');
|
|
130
|
-
lines.push('##
|
|
132
|
+
lines.push('## Quick Start');
|
|
131
133
|
lines.push('');
|
|
132
134
|
lines.push('```typescript');
|
|
133
135
|
lines.push("import { configure } from './hooks';");
|
|
@@ -138,123 +140,22 @@ export function generateHooksAgentsDocs(tables, customOperations) {
|
|
|
138
140
|
lines.push('// Wrap app in <QueryClientProvider client={queryClient}>');
|
|
139
141
|
lines.push('```');
|
|
140
142
|
lines.push('');
|
|
141
|
-
lines.push('##
|
|
143
|
+
lines.push('## Resources');
|
|
144
|
+
lines.push('');
|
|
145
|
+
lines.push(`- **Full API reference:** [README.md](./README.md) — hook docs for all ${tableCount} tables`);
|
|
146
|
+
lines.push('- **Schema types:** [types.ts](./types.ts)');
|
|
147
|
+
lines.push('- **Hooks module:** [hooks.ts](./hooks.ts)');
|
|
148
|
+
lines.push('');
|
|
149
|
+
lines.push('## Conventions');
|
|
150
|
+
lines.push('');
|
|
151
|
+
lines.push('- Query hooks: `use<PluralName>Query`, `use<SingularName>Query`');
|
|
152
|
+
lines.push('- Mutation hooks: `useCreate<Name>Mutation`, `useUpdate<Name>Mutation`, `useDelete<Name>Mutation`');
|
|
153
|
+
lines.push('- All hooks accept a `selection` parameter to pick fields');
|
|
154
|
+
lines.push('');
|
|
155
|
+
lines.push('## Boundaries');
|
|
156
|
+
lines.push('');
|
|
157
|
+
lines.push('All files in this directory are generated. Do not edit manually.');
|
|
142
158
|
lines.push('');
|
|
143
|
-
for (const table of tables) {
|
|
144
|
-
const { singularName, pluralName } = getTableNames(table);
|
|
145
|
-
const pk = getPrimaryKeyInfo(table)[0];
|
|
146
|
-
const scalarFields = getScalarFields(table);
|
|
147
|
-
lines.push(`### HOOK: ${getListQueryHookName(table)}`);
|
|
148
|
-
lines.push('');
|
|
149
|
-
lines.push(`${table.description || `List all ${pluralName}`}.`);
|
|
150
|
-
lines.push('');
|
|
151
|
-
lines.push('```');
|
|
152
|
-
lines.push(`TYPE: query`);
|
|
153
|
-
lines.push(`USAGE: ${getListQueryHookName(table)}({ selection: { fields: { ... } } })`);
|
|
154
|
-
lines.push('');
|
|
155
|
-
lines.push('INPUT:');
|
|
156
|
-
lines.push(' selection: { fields: Record<string, boolean> } - Fields to select');
|
|
157
|
-
lines.push('');
|
|
158
|
-
lines.push('OUTPUT: UseQueryResult<Array<{');
|
|
159
|
-
for (const f of scalarFields) {
|
|
160
|
-
lines.push(` ${f.name}: ${fieldTypeToTs(f.type)}`);
|
|
161
|
-
}
|
|
162
|
-
lines.push('}>>');
|
|
163
|
-
lines.push('```');
|
|
164
|
-
lines.push('');
|
|
165
|
-
if (hasValidPrimaryKey(table)) {
|
|
166
|
-
lines.push(`### HOOK: ${getSingleQueryHookName(table)}`);
|
|
167
|
-
lines.push('');
|
|
168
|
-
lines.push(`${table.description || `Get a single ${singularName} by ${pk.name}`}.`);
|
|
169
|
-
lines.push('');
|
|
170
|
-
lines.push('```');
|
|
171
|
-
lines.push(`TYPE: query`);
|
|
172
|
-
lines.push(`USAGE: ${getSingleQueryHookName(table)}({ ${pk.name}: '<value>', selection: { fields: { ... } } })`);
|
|
173
|
-
lines.push('');
|
|
174
|
-
lines.push('INPUT:');
|
|
175
|
-
lines.push(` ${pk.name}: ${pk.tsType} (required)`);
|
|
176
|
-
lines.push(' selection: { fields: Record<string, boolean> } - Fields to select');
|
|
177
|
-
lines.push('');
|
|
178
|
-
lines.push('OUTPUT: UseQueryResult<{');
|
|
179
|
-
for (const f of scalarFields) {
|
|
180
|
-
lines.push(` ${f.name}: ${fieldTypeToTs(f.type)}`);
|
|
181
|
-
}
|
|
182
|
-
lines.push('}>');
|
|
183
|
-
lines.push('```');
|
|
184
|
-
lines.push('');
|
|
185
|
-
}
|
|
186
|
-
lines.push(`### HOOK: ${getCreateMutationHookName(table)}`);
|
|
187
|
-
lines.push('');
|
|
188
|
-
lines.push(`${table.description || `Create a new ${singularName}`}.`);
|
|
189
|
-
lines.push('');
|
|
190
|
-
lines.push('```');
|
|
191
|
-
lines.push('TYPE: mutation');
|
|
192
|
-
lines.push(`USAGE: const { mutate } = ${getCreateMutationHookName(table)}({ selection: { fields: { ... } } })`);
|
|
193
|
-
lines.push('');
|
|
194
|
-
lines.push('OUTPUT: UseMutationResult');
|
|
195
|
-
lines.push('```');
|
|
196
|
-
lines.push('');
|
|
197
|
-
if (hasValidPrimaryKey(table)) {
|
|
198
|
-
lines.push(`### HOOK: ${getUpdateMutationHookName(table)}`);
|
|
199
|
-
lines.push('');
|
|
200
|
-
lines.push(`${table.description || `Update an existing ${singularName}`}.`);
|
|
201
|
-
lines.push('');
|
|
202
|
-
lines.push('```');
|
|
203
|
-
lines.push('TYPE: mutation');
|
|
204
|
-
lines.push(`USAGE: const { mutate } = ${getUpdateMutationHookName(table)}({ selection: { fields: { ... } } })`);
|
|
205
|
-
lines.push('');
|
|
206
|
-
lines.push('OUTPUT: UseMutationResult');
|
|
207
|
-
lines.push('```');
|
|
208
|
-
lines.push('');
|
|
209
|
-
lines.push(`### HOOK: ${getDeleteMutationHookName(table)}`);
|
|
210
|
-
lines.push('');
|
|
211
|
-
lines.push(`${table.description || `Delete a ${singularName}`}.`);
|
|
212
|
-
lines.push('');
|
|
213
|
-
lines.push('```');
|
|
214
|
-
lines.push('TYPE: mutation');
|
|
215
|
-
lines.push(`USAGE: const { mutate } = ${getDeleteMutationHookName(table)}({})`);
|
|
216
|
-
lines.push('');
|
|
217
|
-
lines.push('OUTPUT: UseMutationResult');
|
|
218
|
-
lines.push('```');
|
|
219
|
-
lines.push('');
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (customOperations.length > 0) {
|
|
223
|
-
lines.push('## CUSTOM OPERATION HOOKS');
|
|
224
|
-
lines.push('');
|
|
225
|
-
for (const op of customOperations) {
|
|
226
|
-
const hookName = getCustomHookName(op);
|
|
227
|
-
lines.push(`### HOOK: ${hookName}`);
|
|
228
|
-
lines.push('');
|
|
229
|
-
lines.push(op.description || op.name);
|
|
230
|
-
lines.push('');
|
|
231
|
-
lines.push('```');
|
|
232
|
-
lines.push(`TYPE: ${op.kind}`);
|
|
233
|
-
if (op.args.length > 0) {
|
|
234
|
-
if (op.kind === 'mutation') {
|
|
235
|
-
lines.push(`USAGE: const { mutate } = ${hookName}()`);
|
|
236
|
-
lines.push(` mutate({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} })`);
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
lines.push(`USAGE: ${hookName}({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} })`);
|
|
240
|
-
}
|
|
241
|
-
lines.push('');
|
|
242
|
-
lines.push('INPUT:');
|
|
243
|
-
for (const arg of op.args) {
|
|
244
|
-
lines.push(` ${arg.name}: ${formatArgType(arg)}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
lines.push(`USAGE: ${hookName}()`);
|
|
249
|
-
lines.push('');
|
|
250
|
-
lines.push('INPUT: none');
|
|
251
|
-
}
|
|
252
|
-
lines.push('');
|
|
253
|
-
lines.push(`OUTPUT: ${op.kind === 'query' ? 'UseQueryResult' : 'UseMutationResult'}`);
|
|
254
|
-
lines.push('```');
|
|
255
|
-
lines.push('');
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
159
|
return {
|
|
259
160
|
fileName: 'AGENTS.md',
|
|
260
161
|
content: lines.join('\n'),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toKebabCase } from 'komoji';
|
|
2
|
-
import { buildSkillFile, buildSkillReference, formatArgType, getEditableFields, getReadmeHeader, getReadmeFooter, gqlTypeToJsonSchemaType, } from '../docs-utils';
|
|
3
|
-
import { getScalarFields, getTableNames, getPrimaryKeyInfo, lcFirst,
|
|
2
|
+
import { buildSkillFile, buildSkillReference, formatArgType, getEditableFields, categorizeSpecialFields, buildSpecialFieldsMarkdown, getReadmeHeader, getReadmeFooter, gqlTypeToJsonSchemaType, } from '../docs-utils';
|
|
3
|
+
import { getScalarFields, getTableNames, getPrimaryKeyInfo, lcFirst, } from '../utils';
|
|
4
4
|
export function generateOrmReadme(tables, customOperations) {
|
|
5
5
|
const lines = [];
|
|
6
6
|
lines.push(...getReadmeHeader('ORM Client'));
|
|
@@ -64,6 +64,8 @@ export function generateOrmReadme(tables, customOperations) {
|
|
|
64
64
|
lines.push(`const deleted = await db.${singularName}.delete({ where: { ${pk.name}: '<value>' } }).execute();`);
|
|
65
65
|
lines.push('```');
|
|
66
66
|
lines.push('');
|
|
67
|
+
const ormSpecialGroups = categorizeSpecialFields(table);
|
|
68
|
+
lines.push(...buildSpecialFieldsMarkdown(ormSpecialGroups));
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
if (customOperations.length > 0) {
|
|
@@ -107,17 +109,19 @@ export function generateOrmReadme(tables, customOperations) {
|
|
|
107
109
|
}
|
|
108
110
|
export function generateOrmAgentsDocs(tables, customOperations) {
|
|
109
111
|
const lines = [];
|
|
110
|
-
|
|
112
|
+
const tableCount = tables.length;
|
|
113
|
+
const customOpCount = customOperations.length;
|
|
114
|
+
lines.push('# ORM Client');
|
|
111
115
|
lines.push('');
|
|
112
116
|
lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
|
|
113
|
-
lines.push('> This document is structured for LLM/agent consumption.');
|
|
114
117
|
lines.push('');
|
|
115
|
-
lines.push('##
|
|
118
|
+
lines.push('## Stack');
|
|
116
119
|
lines.push('');
|
|
117
|
-
lines.push('Prisma-like ORM client for
|
|
118
|
-
lines.push('
|
|
120
|
+
lines.push('- Prisma-like ORM client for a GraphQL API (TypeScript)');
|
|
121
|
+
lines.push(`- ${tableCount} model${tableCount !== 1 ? 's' : ''}${customOpCount > 0 ? `, ${customOpCount} custom operation${customOpCount !== 1 ? 's' : ''}` : ''}`);
|
|
122
|
+
lines.push('- All methods return a query builder; call `.execute()` to run');
|
|
119
123
|
lines.push('');
|
|
120
|
-
lines.push('##
|
|
124
|
+
lines.push('## Quick Start');
|
|
121
125
|
lines.push('');
|
|
122
126
|
lines.push('```typescript');
|
|
123
127
|
lines.push("import { createClient } from './orm';");
|
|
@@ -128,88 +132,22 @@ export function generateOrmAgentsDocs(tables, customOperations) {
|
|
|
128
132
|
lines.push('});');
|
|
129
133
|
lines.push('```');
|
|
130
134
|
lines.push('');
|
|
131
|
-
lines.push('##
|
|
135
|
+
lines.push('## Resources');
|
|
132
136
|
lines.push('');
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const scalarFields = getScalarFields(table);
|
|
137
|
-
const editableFields = getEditableFields(table);
|
|
138
|
-
lines.push(`### MODEL: ${singularName}`);
|
|
139
|
-
lines.push('');
|
|
140
|
-
lines.push(`Access: \`db.${singularName}\``);
|
|
141
|
-
lines.push('');
|
|
142
|
-
lines.push('```');
|
|
143
|
-
lines.push('METHODS:');
|
|
144
|
-
lines.push(` db.${singularName}.findMany({ select, where?, orderBy?, first?, offset? })`);
|
|
145
|
-
lines.push(` db.${singularName}.findOne({ ${pk.name}, select })`);
|
|
146
|
-
lines.push(` db.${singularName}.create({ data: { ${editableFields.map((f) => f.name).join(', ')} }, select })`);
|
|
147
|
-
lines.push(` db.${singularName}.update({ where: { ${pk.name} }, data, select })`);
|
|
148
|
-
lines.push(` db.${singularName}.delete({ where: { ${pk.name} } })`);
|
|
149
|
-
lines.push('');
|
|
150
|
-
lines.push('FIELDS:');
|
|
151
|
-
for (const f of scalarFields) {
|
|
152
|
-
const isPk = f.name === pk.name;
|
|
153
|
-
lines.push(` ${f.name}: ${fieldTypeToTs(f.type)}${isPk ? ' (primary key)' : ''}`);
|
|
154
|
-
}
|
|
155
|
-
lines.push('');
|
|
156
|
-
lines.push('EDITABLE FIELDS:');
|
|
157
|
-
for (const f of editableFields) {
|
|
158
|
-
lines.push(` ${f.name}: ${fieldTypeToTs(f.type)}`);
|
|
159
|
-
}
|
|
160
|
-
lines.push('');
|
|
161
|
-
lines.push('OUTPUT: Promise<JSON>');
|
|
162
|
-
lines.push(` findMany: [{ ${scalarFields.map((f) => f.name).join(', ')} }]`);
|
|
163
|
-
lines.push(` findOne: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
164
|
-
lines.push(` create: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
165
|
-
lines.push(` update: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
166
|
-
lines.push(` delete: { ${pk.name} }`);
|
|
167
|
-
lines.push('```');
|
|
168
|
-
lines.push('');
|
|
169
|
-
}
|
|
170
|
-
if (customOperations.length > 0) {
|
|
171
|
-
lines.push('## CUSTOM OPERATIONS');
|
|
172
|
-
lines.push('');
|
|
173
|
-
for (const op of customOperations) {
|
|
174
|
-
const accessor = op.kind === 'query' ? 'query' : 'mutation';
|
|
175
|
-
lines.push(`### OPERATION: ${op.name}`);
|
|
176
|
-
lines.push('');
|
|
177
|
-
lines.push(op.description || op.name);
|
|
178
|
-
lines.push('');
|
|
179
|
-
lines.push('```');
|
|
180
|
-
lines.push(`TYPE: ${op.kind}`);
|
|
181
|
-
lines.push(`ACCESS: db.${accessor}.${op.name}`);
|
|
182
|
-
if (op.args.length > 0) {
|
|
183
|
-
lines.push(`USAGE: db.${accessor}.${op.name}({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} }).execute()`);
|
|
184
|
-
lines.push('');
|
|
185
|
-
lines.push('INPUT:');
|
|
186
|
-
for (const arg of op.args) {
|
|
187
|
-
lines.push(` ${arg.name}: ${formatArgType(arg)}`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
lines.push(`USAGE: db.${accessor}.${op.name}().execute()`);
|
|
192
|
-
lines.push('');
|
|
193
|
-
lines.push('INPUT: none');
|
|
194
|
-
}
|
|
195
|
-
lines.push('');
|
|
196
|
-
lines.push('OUTPUT: Promise<JSON>');
|
|
197
|
-
lines.push('```');
|
|
198
|
-
lines.push('');
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
lines.push('## PATTERNS');
|
|
137
|
+
lines.push(`- **Full API reference:** [README.md](./README.md) — model docs for all ${tableCount} tables`);
|
|
138
|
+
lines.push('- **Schema types:** [types.ts](./types.ts)');
|
|
139
|
+
lines.push('- **ORM client:** [orm.ts](./orm.ts)');
|
|
202
140
|
lines.push('');
|
|
203
|
-
lines.push('
|
|
204
|
-
lines.push('// All methods require .execute() to run');
|
|
205
|
-
lines.push('const result = await db.modelName.findMany({ select: { id: true } }).execute();');
|
|
141
|
+
lines.push('## Conventions');
|
|
206
142
|
lines.push('');
|
|
207
|
-
lines.push('
|
|
208
|
-
lines.push('
|
|
143
|
+
lines.push('- Access models via `db.<ModelName>` (e.g. `db.User`)');
|
|
144
|
+
lines.push('- CRUD methods: `findMany`, `findOne`, `create`, `update`, `delete`');
|
|
145
|
+
lines.push('- Always call `.execute()` to run the query');
|
|
146
|
+
lines.push('- Custom operations via `db.query.<name>` or `db.mutation.<name>`');
|
|
209
147
|
lines.push('');
|
|
210
|
-
lines.push('
|
|
211
|
-
lines.push(
|
|
212
|
-
lines.push('
|
|
148
|
+
lines.push('## Boundaries');
|
|
149
|
+
lines.push('');
|
|
150
|
+
lines.push('All files in this directory are generated. Do not edit manually.');
|
|
213
151
|
lines.push('');
|
|
214
152
|
return {
|
|
215
153
|
fileName: 'AGENTS.md',
|
|
@@ -353,11 +291,17 @@ export function generateOrmSkills(tables, customOperations, targetName) {
|
|
|
353
291
|
const modelName = lcFirst(singularName);
|
|
354
292
|
const refName = toKebabCase(singularName);
|
|
355
293
|
referenceNames.push(refName);
|
|
294
|
+
const ormSkillSpecialGroups = categorizeSpecialFields(table);
|
|
295
|
+
const ormSkillBaseDesc = table.description || `ORM operations for ${table.name} records`;
|
|
296
|
+
const ormSkillSpecialDesc = ormSkillSpecialGroups.length > 0
|
|
297
|
+
? ormSkillBaseDesc + '\n\n' +
|
|
298
|
+
ormSkillSpecialGroups.map((g) => `**${g.label}:** ${g.fields.map((f) => `\`${f.name}\``).join(', ')}\n${g.description}`).join('\n\n')
|
|
299
|
+
: ormSkillBaseDesc;
|
|
356
300
|
files.push({
|
|
357
301
|
fileName: `${skillName}/references/${refName}.md`,
|
|
358
302
|
content: buildSkillReference({
|
|
359
303
|
title: singularName,
|
|
360
|
-
description:
|
|
304
|
+
description: ormSkillSpecialDesc,
|
|
361
305
|
language: 'typescript',
|
|
362
306
|
usage: [
|
|
363
307
|
`db.${modelName}.findMany({ select: { id: true } }).execute()`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-codegen",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.1",
|
|
4
4
|
"description": "GraphQL SDK generator for Constructive databases with React Query hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -56,25 +56,25 @@
|
|
|
56
56
|
"@0no-co/graphql.web": "^1.1.2",
|
|
57
57
|
"@babel/generator": "^7.29.1",
|
|
58
58
|
"@babel/types": "^7.29.0",
|
|
59
|
-
"@constructive-io/graphql-query": "^3.6.
|
|
60
|
-
"@constructive-io/graphql-types": "^3.3.
|
|
59
|
+
"@constructive-io/graphql-query": "^3.6.3",
|
|
60
|
+
"@constructive-io/graphql-types": "^3.3.4",
|
|
61
61
|
"@inquirerer/utils": "^3.3.4",
|
|
62
|
-
"@pgpmjs/core": "^6.6.
|
|
62
|
+
"@pgpmjs/core": "^6.6.5",
|
|
63
63
|
"ajv": "^8.18.0",
|
|
64
64
|
"deepmerge": "^4.3.1",
|
|
65
65
|
"find-and-require-package-json": "^0.9.1",
|
|
66
66
|
"gql-ast": "^3.3.3",
|
|
67
|
-
"graphile-schema": "^1.6.
|
|
67
|
+
"graphile-schema": "^1.6.3",
|
|
68
68
|
"graphql": "16.13.0",
|
|
69
69
|
"inflekt": "^0.3.3",
|
|
70
70
|
"inquirerer": "^4.7.0",
|
|
71
71
|
"jiti": "^2.6.1",
|
|
72
72
|
"komoji": "^0.8.1",
|
|
73
73
|
"oxfmt": "^0.40.0",
|
|
74
|
-
"pg-cache": "^3.3.
|
|
75
|
-
"pg-env": "^1.7.
|
|
76
|
-
"pgsql-client": "^3.5.
|
|
77
|
-
"pgsql-seed": "^2.5.
|
|
74
|
+
"pg-cache": "^3.3.4",
|
|
75
|
+
"pg-env": "^1.7.3",
|
|
76
|
+
"pgsql-client": "^3.5.5",
|
|
77
|
+
"pgsql-seed": "^2.5.5",
|
|
78
78
|
"undici": "^7.24.3"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
@@ -101,5 +101,5 @@
|
|
|
101
101
|
"tsx": "^4.21.0",
|
|
102
102
|
"typescript": "^5.9.3"
|
|
103
103
|
},
|
|
104
|
-
"gitHead": "
|
|
104
|
+
"gitHead": "9c322f47ca08b5b853fcb395fe2bfc224f8c4c27"
|
|
105
105
|
}
|