@constructive-io/graphql-codegen 4.14.3 → 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.
@@ -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, fieldTypeToTs, } from './utils';
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
- lines.push('# React Query Hooks - Agent Reference');
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('## OVERVIEW');
126
+ lines.push('## Stack');
126
127
  lines.push('');
127
- lines.push('React Query hooks wrapping ORM operations for data fetching and mutations.');
128
- lines.push('All query hooks return `UseQueryResult`. All mutation hooks return `UseMutationResult`.');
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('## SETUP');
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('## HOOKS');
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, fieldTypeToTs, } from '../utils';
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
- lines.push('# ORM Client - Agent Reference');
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('## OVERVIEW');
118
+ lines.push('## Stack');
116
119
  lines.push('');
117
- lines.push('Prisma-like ORM client for interacting with a GraphQL API.');
118
- lines.push('All methods return a query builder. Call `.execute()` to run the query.');
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('## SETUP');
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('## MODELS');
135
+ lines.push('## Resources');
132
136
  lines.push('');
133
- for (const table of tables) {
134
- const { singularName } = getTableNames(table);
135
- const pk = getPrimaryKeyInfo(table)[0];
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('```typescript');
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('// Select specific fields');
208
- lines.push('const partial = await db.modelName.findMany({ select: { id: true, name: true } }).execute();');
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('// Filter with where clause');
211
- lines.push("const filtered = await db.modelName.findMany({ select: { id: true }, where: { name: 'test' } }).execute();");
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: table.description || `ORM operations for ${table.name} records`,
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.14.3",
3
+ "version": "4.15.0",
4
4
  "description": "GraphQL SDK generator for Constructive databases with React Query hooks",
5
5
  "keywords": [
6
6
  "graphql",
@@ -56,26 +56,26 @@
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.0",
60
- "@constructive-io/graphql-types": "^3.3.2",
61
- "@inquirerer/utils": "^3.3.1",
62
- "@pgpmjs/core": "^6.6.2",
59
+ "@constructive-io/graphql-query": "^3.6.2",
60
+ "@constructive-io/graphql-types": "^3.3.4",
61
+ "@inquirerer/utils": "^3.3.4",
62
+ "@pgpmjs/core": "^6.6.4",
63
63
  "ajv": "^8.18.0",
64
64
  "deepmerge": "^4.3.1",
65
65
  "find-and-require-package-json": "^0.9.1",
66
- "gql-ast": "^3.3.2",
67
- "graphile-schema": "^1.6.0",
68
- "graphql": "^16.13.0",
66
+ "gql-ast": "^3.3.3",
67
+ "graphile-schema": "^1.6.2",
68
+ "graphql": "16.13.0",
69
69
  "inflekt": "^0.3.3",
70
- "inquirerer": "^4.5.2",
70
+ "inquirerer": "^4.7.0",
71
71
  "jiti": "^2.6.1",
72
72
  "komoji": "^0.8.1",
73
- "oxfmt": "^0.36.0",
74
- "pg-cache": "^3.3.2",
75
- "pg-env": "^1.7.2",
76
- "pgsql-client": "^3.5.2",
77
- "pgsql-seed": "^2.5.2",
78
- "undici": "^7.22.0"
73
+ "oxfmt": "^0.40.0",
74
+ "pg-cache": "^3.3.4",
75
+ "pg-env": "^1.7.3",
76
+ "pgsql-client": "^3.5.4",
77
+ "pgsql-seed": "^2.5.4",
78
+ "undici": "^7.24.3"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "@tanstack/react-query": "^5.0.0",
@@ -95,11 +95,11 @@
95
95
  "@types/jest": "^30.0.0",
96
96
  "@types/node": "^22.19.11",
97
97
  "@types/react": "^19.2.14",
98
- "jest": "^30.2.0",
98
+ "jest": "^30.3.0",
99
99
  "react": "^19.2.4",
100
100
  "ts-jest": "^29.2.5",
101
101
  "tsx": "^4.21.0",
102
102
  "typescript": "^5.9.3"
103
103
  },
104
- "gitHead": "edb69fc817642476636394a89b47293b6a0c7c43"
104
+ "gitHead": "8afe6b19da82facbe5f3365762ba52888af5b3c9"
105
105
  }