@constructive-io/graphql-codegen 4.5.2 → 4.6.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.
Files changed (41) hide show
  1. package/core/codegen/hooks-docs-generator.js +16 -16
  2. package/core/codegen/mutations.js +6 -6
  3. package/core/codegen/orm/custom-ops-generator.d.ts +2 -2
  4. package/core/codegen/orm/custom-ops-generator.js +15 -6
  5. package/core/codegen/orm/docs-generator.js +6 -6
  6. package/core/codegen/orm/index.js +4 -3
  7. package/core/codegen/orm/input-types-generator.d.ts +1 -1
  8. package/core/codegen/orm/input-types-generator.js +41 -17
  9. package/core/codegen/queries.js +12 -10
  10. package/core/codegen/schema-types-generator.d.ts +2 -0
  11. package/core/codegen/schema-types-generator.js +41 -12
  12. package/core/codegen/shared/index.js +2 -0
  13. package/core/codegen/utils.d.ts +14 -0
  14. package/core/codegen/utils.js +87 -0
  15. package/core/introspect/infer-tables.d.ts +5 -0
  16. package/core/introspect/infer-tables.js +11 -4
  17. package/core/pipeline/index.js +2 -1
  18. package/esm/core/codegen/hooks-docs-generator.js +16 -16
  19. package/esm/core/codegen/mutations.js +6 -6
  20. package/esm/core/codegen/orm/custom-ops-generator.d.ts +2 -2
  21. package/esm/core/codegen/orm/custom-ops-generator.js +17 -8
  22. package/esm/core/codegen/orm/docs-generator.js +6 -6
  23. package/esm/core/codegen/orm/index.js +4 -3
  24. package/esm/core/codegen/orm/input-types-generator.d.ts +1 -1
  25. package/esm/core/codegen/orm/input-types-generator.js +43 -19
  26. package/esm/core/codegen/queries.js +12 -10
  27. package/esm/core/codegen/schema-types-generator.d.ts +2 -0
  28. package/esm/core/codegen/schema-types-generator.js +43 -14
  29. package/esm/core/codegen/shared/index.js +2 -0
  30. package/esm/core/codegen/utils.d.ts +14 -0
  31. package/esm/core/codegen/utils.js +86 -0
  32. package/esm/core/introspect/infer-tables.d.ts +5 -0
  33. package/esm/core/introspect/infer-tables.js +11 -4
  34. package/esm/core/pipeline/index.js +2 -1
  35. package/esm/types/config.d.ts +6 -0
  36. package/esm/types/config.js +1 -0
  37. package/esm/types/schema.d.ts +4 -0
  38. package/package.json +6 -6
  39. package/types/config.d.ts +6 -0
  40. package/types/config.js +1 -0
  41. package/types/schema.d.ts +4 -0
@@ -115,7 +115,7 @@ function generateCustomScalarTypes(customScalarTypes) {
115
115
  }
116
116
  return statements;
117
117
  }
118
- function generateEnumTypes(typeRegistry, tableTypeNames) {
118
+ function generateEnumTypes(typeRegistry, tableTypeNames, comments = true) {
119
119
  const statements = [];
120
120
  const generatedTypes = new Set();
121
121
  for (const [typeName, typeInfo] of typeRegistry) {
@@ -127,12 +127,17 @@ function generateEnumTypes(typeRegistry, tableTypeNames) {
127
127
  continue;
128
128
  const unionType = t.tsUnionType(typeInfo.enumValues.map((v) => t.tsLiteralType(t.stringLiteral(v))));
129
129
  const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
130
- statements.push(t.exportNamedDeclaration(typeAlias));
130
+ const exportDecl = t.exportNamedDeclaration(typeAlias);
131
+ const enumDescription = (0, utils_1.stripSmartComments)(typeInfo.description, comments);
132
+ if (enumDescription) {
133
+ (0, babel_ast_1.addJSDocComment)(exportDecl, enumDescription.split('\n'));
134
+ }
135
+ statements.push(exportDecl);
131
136
  generatedTypes.add(typeName);
132
137
  }
133
138
  return { statements, generatedTypes };
134
139
  }
135
- function generateInputObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated) {
140
+ function generateInputObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated, comments = true) {
136
141
  const statements = [];
137
142
  const generatedTypes = new Set(alreadyGenerated);
138
143
  const typesToGenerate = new Set();
@@ -164,6 +169,10 @@ function generateInputObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated
164
169
  const tsType = typeRefToTs(field.type);
165
170
  const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(tsType))));
166
171
  prop.optional = optional;
172
+ const fieldDescription = (0, utils_1.stripSmartComments)(field.description, comments);
173
+ if (fieldDescription) {
174
+ (0, babel_ast_1.addJSDocComment)(prop, fieldDescription.split('\n'));
175
+ }
167
176
  properties.push(prop);
168
177
  const baseType = (0, type_resolver_1.getTypeBaseName)(field.type);
169
178
  if (baseType &&
@@ -177,11 +186,16 @@ function generateInputObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated
177
186
  }
178
187
  }
179
188
  const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(typeName), null, null, t.tsInterfaceBody(properties));
180
- statements.push(t.exportNamedDeclaration(interfaceDecl));
189
+ const exportDecl = t.exportNamedDeclaration(interfaceDecl);
190
+ const inputDescription = (0, utils_1.stripSmartComments)(typeInfo.description, comments);
191
+ if (inputDescription) {
192
+ (0, babel_ast_1.addJSDocComment)(exportDecl, inputDescription.split('\n'));
193
+ }
194
+ statements.push(exportDecl);
181
195
  }
182
196
  return { statements, generatedTypes };
183
197
  }
184
- function generateUnionTypes(typeRegistry, tableTypeNames, alreadyGenerated) {
198
+ function generateUnionTypes(typeRegistry, tableTypeNames, alreadyGenerated, comments = true) {
185
199
  const statements = [];
186
200
  const generatedTypes = new Set(alreadyGenerated);
187
201
  for (const [typeName, typeInfo] of typeRegistry) {
@@ -195,7 +209,12 @@ function generateUnionTypes(typeRegistry, tableTypeNames, alreadyGenerated) {
195
209
  continue;
196
210
  const unionType = t.tsUnionType(typeInfo.possibleTypes.map((pt) => t.tsTypeReference(t.identifier(pt))));
197
211
  const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
198
- statements.push(t.exportNamedDeclaration(typeAlias));
212
+ const exportDecl = t.exportNamedDeclaration(typeAlias);
213
+ const unionDescription = (0, utils_1.stripSmartComments)(typeInfo.description, comments);
214
+ if (unionDescription) {
215
+ (0, babel_ast_1.addJSDocComment)(exportDecl, unionDescription.split('\n'));
216
+ }
217
+ statements.push(exportDecl);
199
218
  generatedTypes.add(typeName);
200
219
  }
201
220
  return { statements, generatedTypes };
@@ -223,7 +242,7 @@ function collectReturnTypesFromRootTypes(typeRegistry, tableTypeNames) {
223
242
  processFields(mutationType.fields);
224
243
  return returnTypes;
225
244
  }
226
- function generatePayloadObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated) {
245
+ function generatePayloadObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated, comments = true) {
227
246
  const statements = [];
228
247
  const generatedTypes = new Set(alreadyGenerated);
229
248
  const referencedTableTypes = new Set();
@@ -256,6 +275,10 @@ function generatePayloadObjectTypes(typeRegistry, tableTypeNames, alreadyGenerat
256
275
  const finalType = isNullable ? `${tsType} | null` : tsType;
257
276
  const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(finalType))));
258
277
  prop.optional = isNullable;
278
+ const fieldDescription = (0, utils_1.stripSmartComments)(field.description, comments);
279
+ if (fieldDescription) {
280
+ (0, babel_ast_1.addJSDocComment)(prop, fieldDescription.split('\n'));
281
+ }
259
282
  properties.push(prop);
260
283
  if (baseType && tableTypeNames.has(baseType)) {
261
284
  referencedTableTypes.add(baseType);
@@ -271,22 +294,28 @@ function generatePayloadObjectTypes(typeRegistry, tableTypeNames, alreadyGenerat
271
294
  }
272
295
  }
273
296
  const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(typeName), null, null, t.tsInterfaceBody(properties));
274
- statements.push(t.exportNamedDeclaration(interfaceDecl));
297
+ const exportDecl = t.exportNamedDeclaration(interfaceDecl);
298
+ const payloadDescription = (0, utils_1.stripSmartComments)(typeInfo.description, comments);
299
+ if (payloadDescription) {
300
+ (0, babel_ast_1.addJSDocComment)(exportDecl, payloadDescription.split('\n'));
301
+ }
302
+ statements.push(exportDecl);
275
303
  }
276
304
  return { statements, generatedTypes, referencedTableTypes };
277
305
  }
278
306
  function generateSchemaTypesFile(options) {
279
307
  const { typeRegistry, tableTypeNames } = options;
308
+ const comments = options.comments !== false;
280
309
  const allStatements = [];
281
310
  let generatedTypes = new Set();
282
311
  const customScalarTypes = collectCustomScalarTypes(typeRegistry);
283
- const enumResult = generateEnumTypes(typeRegistry, tableTypeNames);
312
+ const enumResult = generateEnumTypes(typeRegistry, tableTypeNames, comments);
284
313
  generatedTypes = new Set([...generatedTypes, ...enumResult.generatedTypes]);
285
- const unionResult = generateUnionTypes(typeRegistry, tableTypeNames, generatedTypes);
314
+ const unionResult = generateUnionTypes(typeRegistry, tableTypeNames, generatedTypes, comments);
286
315
  generatedTypes = new Set([...generatedTypes, ...unionResult.generatedTypes]);
287
- const inputResult = generateInputObjectTypes(typeRegistry, tableTypeNames, generatedTypes);
316
+ const inputResult = generateInputObjectTypes(typeRegistry, tableTypeNames, generatedTypes, comments);
288
317
  generatedTypes = new Set([...generatedTypes, ...inputResult.generatedTypes]);
289
- const payloadResult = generatePayloadObjectTypes(typeRegistry, tableTypeNames, generatedTypes);
318
+ const payloadResult = generatePayloadObjectTypes(typeRegistry, tableTypeNames, generatedTypes, comments);
290
319
  const referencedTableTypes = Array.from(payloadResult.referencedTableTypes).sort();
291
320
  const baseFilterImports = Array.from(scalars_1.BASE_FILTER_TYPE_NAMES).sort();
292
321
  const allTypesImports = [...referencedTableTypes, ...baseFilterImports];
@@ -64,6 +64,7 @@ function exportAllFrom(modulePath) {
64
64
  */
65
65
  function generateSharedTypes(options) {
66
66
  const { tables, customOperations } = options;
67
+ const commentsEnabled = options.config.codegen?.comments !== false;
67
68
  const files = [];
68
69
  // Collect table type names for import path resolution
69
70
  const tableTypeNames = new Set(tables.map((t) => (0, utils_1.getTableNames)(t).typeName));
@@ -75,6 +76,7 @@ function generateSharedTypes(options) {
75
76
  const schemaTypesResult = (0, schema_types_generator_1.generateSchemaTypesFile)({
76
77
  typeRegistry: customOperations.typeRegistry,
77
78
  tableTypeNames,
79
+ comments: commentsEnabled,
78
80
  });
79
81
  // Only include if there's meaningful content
80
82
  if (schemaTypesResult.content.split('\n').length > 10) {
@@ -179,6 +179,20 @@ export declare function hasValidPrimaryKey(table: CleanTable): boolean;
179
179
  * e.g., "cars" for list queries, "car" for detail queries
180
180
  */
181
181
  export declare function getQueryKeyPrefix(table: CleanTable): string;
182
+ /**
183
+ * Strip PostGraphile smart comments and boilerplate from a description string.
184
+ *
185
+ * Smart comments are lines starting with `@` (e.g., `@omit`, `@name newName`).
186
+ * Boilerplate descriptions are generic PostGraphile-generated text that repeats
187
+ * on every mutation input, clientMutationId field, etc.
188
+ *
189
+ * This returns only the meaningful human-readable portion of the comment,
190
+ * or undefined if the result is empty or boilerplate.
191
+ *
192
+ * @param description - Raw description from GraphQL introspection
193
+ * @returns Cleaned description, or undefined if empty/boilerplate
194
+ */
195
+ export declare function stripSmartComments(description: string | null | undefined, enabled?: boolean): string | undefined;
182
196
  /**
183
197
  * Generate a doc comment header for generated files
184
198
  */
@@ -37,6 +37,7 @@ exports.getPrimaryKeyInfo = getPrimaryKeyInfo;
37
37
  exports.getPrimaryKeyFields = getPrimaryKeyFields;
38
38
  exports.hasValidPrimaryKey = hasValidPrimaryKey;
39
39
  exports.getQueryKeyPrefix = getQueryKeyPrefix;
40
+ exports.stripSmartComments = stripSmartComments;
40
41
  exports.getGeneratedFileHeader = getGeneratedFileHeader;
41
42
  exports.indent = indent;
42
43
  /**
@@ -364,6 +365,92 @@ function getQueryKeyPrefix(table) {
364
365
  return lcFirst(table.name);
365
366
  }
366
367
  // ============================================================================
368
+ // Smart Comment Utilities
369
+ // ============================================================================
370
+ /**
371
+ * PostGraphile smart comment tags that should be stripped from descriptions.
372
+ * Smart comments start with `@` and control PostGraphile behavior
373
+ * (e.g., `@omit`, `@name`, `@foreignKey`, etc.)
374
+ *
375
+ * A PostgreSQL COMMENT may contain both human-readable text and smart comments:
376
+ * COMMENT ON TABLE users IS 'User accounts for the application\n@omit delete';
377
+ *
378
+ * PostGraphile's introspection already separates these: the GraphQL `description`
379
+ * field contains only the human-readable part. So in most cases, the description
380
+ * we receive from introspection is already clean.
381
+ *
382
+ * However, as a safety measure, this utility strips any remaining `@`-prefixed
383
+ * lines that may have leaked through.
384
+ */
385
+ /**
386
+ * PostGraphile auto-generated boilerplate descriptions that add no value.
387
+ * These are generic descriptions PostGraphile puts on every mutation input,
388
+ * clientMutationId field, etc. We filter them out to keep generated code clean.
389
+ */
390
+ const POSTGRAPHILE_BOILERPLATE = [
391
+ 'The exclusive input argument for this mutation.',
392
+ 'An arbitrary string value with no semantic meaning.',
393
+ 'The exact same `clientMutationId` that was provided in the mutation input,',
394
+ 'The output of our',
395
+ 'All input for the',
396
+ 'A cursor for use in pagination.',
397
+ 'An edge for our',
398
+ 'Information to aid in pagination.',
399
+ 'Reads and enables pagination through a set of',
400
+ 'A list of edges which contains the',
401
+ 'The count of *all* `',
402
+ 'A list of `',
403
+ 'Our root query field',
404
+ 'Reads a single',
405
+ 'The root query type',
406
+ 'The root mutation type',
407
+ ];
408
+ /**
409
+ * Check if a description is generic PostGraphile boilerplate that should be suppressed.
410
+ */
411
+ function isBoilerplateDescription(description) {
412
+ const trimmed = description.trim();
413
+ return POSTGRAPHILE_BOILERPLATE.some((bp) => trimmed.startsWith(bp));
414
+ }
415
+ /**
416
+ * Strip PostGraphile smart comments and boilerplate from a description string.
417
+ *
418
+ * Smart comments are lines starting with `@` (e.g., `@omit`, `@name newName`).
419
+ * Boilerplate descriptions are generic PostGraphile-generated text that repeats
420
+ * on every mutation input, clientMutationId field, etc.
421
+ *
422
+ * This returns only the meaningful human-readable portion of the comment,
423
+ * or undefined if the result is empty or boilerplate.
424
+ *
425
+ * @param description - Raw description from GraphQL introspection
426
+ * @returns Cleaned description, or undefined if empty/boilerplate
427
+ */
428
+ function stripSmartComments(description, enabled = true) {
429
+ if (!enabled)
430
+ return undefined;
431
+ if (!description)
432
+ return undefined;
433
+ // Check if entire description is boilerplate
434
+ if (isBoilerplateDescription(description))
435
+ return undefined;
436
+ const lines = description.split('\n');
437
+ const cleanLines = [];
438
+ for (const line of lines) {
439
+ const trimmed = line.trim();
440
+ // Skip lines that start with @ (smart comment directives)
441
+ if (trimmed.startsWith('@'))
442
+ continue;
443
+ cleanLines.push(line);
444
+ }
445
+ const result = cleanLines.join('\n').trim();
446
+ if (result.length === 0)
447
+ return undefined;
448
+ // Re-check after stripping smart comments
449
+ if (isBoilerplateDescription(result))
450
+ return undefined;
451
+ return result;
452
+ }
453
+ // ============================================================================
367
454
  // Code generation helpers
368
455
  // ============================================================================
369
456
  /**
@@ -25,6 +25,11 @@ export interface InferTablesOptions {
25
25
  * Custom pattern overrides (for non-standard PostGraphile configurations)
26
26
  */
27
27
  patterns?: Partial<typeof PATTERNS>;
28
+ /**
29
+ * Include PostgreSQL COMMENT descriptions on tables and fields.
30
+ * @default true
31
+ */
32
+ comments?: boolean;
28
33
  }
29
34
  /**
30
35
  * Infer CleanTable[] from GraphQL introspection by recognizing PostGraphile patterns
@@ -16,6 +16,7 @@ exports.inferTablesFromIntrospection = inferTablesFromIntrospection;
16
16
  * - Mutation operations: create{Name}, update{Name}, delete{Name}
17
17
  */
18
18
  const inflekt_1 = require("inflekt");
19
+ const utils_1 = require("../codegen/utils");
19
20
  const introspection_1 = require("../../types/introspection");
20
21
  // ============================================================================
21
22
  // Pattern Matching Constants
@@ -84,6 +85,7 @@ function isInternalType(name) {
84
85
  function inferTablesFromIntrospection(introspection, options = {}) {
85
86
  const { __schema: schema } = introspection;
86
87
  const { types, queryType, mutationType } = schema;
88
+ const commentsEnabled = options.comments !== false;
87
89
  // Build lookup maps for efficient access
88
90
  const typeMap = buildTypeMap(types);
89
91
  const { entityNames, entityToConnection, connectionToEntity } = buildEntityConnectionMaps(types, typeMap);
@@ -98,7 +100,7 @@ function inferTablesFromIntrospection(introspection, options = {}) {
98
100
  if (!entityType)
99
101
  continue;
100
102
  // Infer all metadata for this entity
101
- const { table, hasRealOperation } = buildCleanTable(entityName, entityType, typeMap, queryFields, mutationFields, entityToConnection, connectionToEntity);
103
+ const { table, hasRealOperation } = buildCleanTable(entityName, entityType, typeMap, queryFields, mutationFields, entityToConnection, connectionToEntity, commentsEnabled);
102
104
  // Only include tables that have at least one real operation
103
105
  if (hasRealOperation) {
104
106
  tables.push(table);
@@ -172,9 +174,9 @@ function resolveEntityNameFromConnectionType(connectionType, typeMap) {
172
174
  /**
173
175
  * Build a complete CleanTable from an entity type
174
176
  */
175
- function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationFields, entityToConnection, connectionToEntity) {
177
+ function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationFields, entityToConnection, connectionToEntity, commentsEnabled) {
176
178
  // Extract scalar fields from entity type
177
- const fields = extractEntityFields(entityType, typeMap, entityToConnection);
179
+ const fields = extractEntityFields(entityType, typeMap, entityToConnection, commentsEnabled);
178
180
  // Infer relations from entity fields
179
181
  const relations = inferRelations(entityType, entityToConnection, connectionToEntity);
180
182
  // Match query and mutation operations
@@ -202,9 +204,12 @@ function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationF
202
204
  delete: mutationOps.delete,
203
205
  patchFieldName,
204
206
  };
207
+ // Extract description from entity type (PostgreSQL COMMENT), strip smart comments
208
+ const description = commentsEnabled ? (0, utils_1.stripSmartComments)(entityType.description) : undefined;
205
209
  return {
206
210
  table: {
207
211
  name: entityName,
212
+ ...(description ? { description } : {}),
208
213
  fields,
209
214
  relations,
210
215
  inflection,
@@ -221,7 +226,7 @@ function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationF
221
226
  * Extract scalar fields from an entity type
222
227
  * Excludes relation fields (those returning other entity types or connections)
223
228
  */
224
- function extractEntityFields(entityType, typeMap, entityToConnection) {
229
+ function extractEntityFields(entityType, typeMap, entityToConnection, commentsEnabled) {
225
230
  const fields = [];
226
231
  if (!entityType.fields)
227
232
  return fields;
@@ -239,8 +244,10 @@ function extractEntityFields(entityType, typeMap, entityToConnection) {
239
244
  }
240
245
  }
241
246
  // Include scalar, enum, and other non-relation fields
247
+ const fieldDescription = commentsEnabled ? (0, utils_1.stripSmartComments)(field.description) : undefined;
242
248
  fields.push({
243
249
  name: field.name,
250
+ ...(fieldDescription ? { description: fieldDescription } : {}),
244
251
  type: convertToCleanFieldType(field.type),
245
252
  });
246
253
  }
@@ -30,7 +30,8 @@ async function runCodegenPipeline(options) {
30
30
  const { introspection } = await source.fetch();
31
31
  // 2. Infer tables from introspection (replaces _meta)
32
32
  log('Inferring table metadata from schema...');
33
- let tables = (0, infer_tables_1.inferTablesFromIntrospection)(introspection);
33
+ const commentsEnabled = config.codegen?.comments !== false;
34
+ let tables = (0, infer_tables_1.inferTablesFromIntrospection)(introspection, { comments: commentsEnabled });
34
35
  const totalTables = tables.length;
35
36
  log(` Found ${totalTables} tables`);
36
37
  // 3. Filter tables by config (combine exclude and systemExclude)
@@ -37,14 +37,14 @@ export function generateHooksReadme(tables, customOperations) {
37
37
  lines.push('|------|------|-------------|');
38
38
  for (const table of tables) {
39
39
  const { singularName, pluralName } = getTableNames(table);
40
- lines.push(`| \`${getListQueryHookName(table)}\` | Query | List all ${pluralName} |`);
40
+ lines.push(`| \`${getListQueryHookName(table)}\` | Query | ${table.description || `List all ${pluralName}`} |`);
41
41
  if (hasValidPrimaryKey(table)) {
42
- lines.push(`| \`${getSingleQueryHookName(table)}\` | Query | Get one ${singularName} |`);
42
+ lines.push(`| \`${getSingleQueryHookName(table)}\` | Query | ${table.description || `Get one ${singularName}`} |`);
43
43
  }
44
- lines.push(`| \`${getCreateMutationHookName(table)}\` | Mutation | Create a ${singularName} |`);
44
+ lines.push(`| \`${getCreateMutationHookName(table)}\` | Mutation | ${table.description || `Create a ${singularName}`} |`);
45
45
  if (hasValidPrimaryKey(table)) {
46
- lines.push(`| \`${getUpdateMutationHookName(table)}\` | Mutation | Update a ${singularName} |`);
47
- lines.push(`| \`${getDeleteMutationHookName(table)}\` | Mutation | Delete a ${singularName} |`);
46
+ lines.push(`| \`${getUpdateMutationHookName(table)}\` | Mutation | ${table.description || `Update a ${singularName}`} |`);
47
+ lines.push(`| \`${getDeleteMutationHookName(table)}\` | Mutation | ${table.description || `Delete a ${singularName}`} |`);
48
48
  }
49
49
  }
50
50
  for (const op of customOperations) {
@@ -145,7 +145,7 @@ export function generateHooksAgentsDocs(tables, customOperations) {
145
145
  const scalarFields = getScalarFields(table);
146
146
  lines.push(`### HOOK: ${getListQueryHookName(table)}`);
147
147
  lines.push('');
148
- lines.push(`List all ${pluralName}.`);
148
+ lines.push(`${table.description || `List all ${pluralName}`}.`);
149
149
  lines.push('');
150
150
  lines.push('```');
151
151
  lines.push(`TYPE: query`);
@@ -164,7 +164,7 @@ export function generateHooksAgentsDocs(tables, customOperations) {
164
164
  if (hasValidPrimaryKey(table)) {
165
165
  lines.push(`### HOOK: ${getSingleQueryHookName(table)}`);
166
166
  lines.push('');
167
- lines.push(`Get a single ${singularName} by ${pk.name}.`);
167
+ lines.push(`${table.description || `Get a single ${singularName} by ${pk.name}`}.`);
168
168
  lines.push('');
169
169
  lines.push('```');
170
170
  lines.push(`TYPE: query`);
@@ -184,7 +184,7 @@ export function generateHooksAgentsDocs(tables, customOperations) {
184
184
  }
185
185
  lines.push(`### HOOK: ${getCreateMutationHookName(table)}`);
186
186
  lines.push('');
187
- lines.push(`Create a new ${singularName}.`);
187
+ lines.push(`${table.description || `Create a new ${singularName}`}.`);
188
188
  lines.push('');
189
189
  lines.push('```');
190
190
  lines.push('TYPE: mutation');
@@ -196,7 +196,7 @@ export function generateHooksAgentsDocs(tables, customOperations) {
196
196
  if (hasValidPrimaryKey(table)) {
197
197
  lines.push(`### HOOK: ${getUpdateMutationHookName(table)}`);
198
198
  lines.push('');
199
- lines.push(`Update an existing ${singularName}.`);
199
+ lines.push(`${table.description || `Update an existing ${singularName}`}.`);
200
200
  lines.push('');
201
201
  lines.push('```');
202
202
  lines.push('TYPE: mutation');
@@ -207,7 +207,7 @@ export function generateHooksAgentsDocs(tables, customOperations) {
207
207
  lines.push('');
208
208
  lines.push(`### HOOK: ${getDeleteMutationHookName(table)}`);
209
209
  lines.push('');
210
- lines.push(`Delete a ${singularName}.`);
210
+ lines.push(`${table.description || `Delete a ${singularName}`}.`);
211
211
  lines.push('');
212
212
  lines.push('```');
213
213
  lines.push('TYPE: mutation');
@@ -267,7 +267,7 @@ export function getHooksMcpTools(tables, customOperations) {
267
267
  const scalarFields = getScalarFields(table);
268
268
  tools.push({
269
269
  name: `hooks_${lcFirst(pluralName)}_query`,
270
- description: `React Query hook to list all ${pluralName}`,
270
+ description: table.description || `React Query hook to list all ${pluralName}`,
271
271
  inputSchema: {
272
272
  type: 'object',
273
273
  properties: {
@@ -281,7 +281,7 @@ export function getHooksMcpTools(tables, customOperations) {
281
281
  if (hasValidPrimaryKey(table)) {
282
282
  tools.push({
283
283
  name: `hooks_${lcFirst(singularName)}_query`,
284
- description: `React Query hook to get a single ${singularName} by ${pk.name}`,
284
+ description: table.description || `React Query hook to get a single ${singularName} by ${pk.name}`,
285
285
  inputSchema: {
286
286
  type: 'object',
287
287
  properties: {
@@ -296,7 +296,7 @@ export function getHooksMcpTools(tables, customOperations) {
296
296
  }
297
297
  tools.push({
298
298
  name: `hooks_create_${lcFirst(singularName)}_mutation`,
299
- description: `React Query mutation hook to create a ${singularName}`,
299
+ description: table.description || `React Query mutation hook to create a ${singularName}`,
300
300
  inputSchema: {
301
301
  type: 'object',
302
302
  properties: Object.fromEntries(scalarFields
@@ -316,7 +316,7 @@ export function getHooksMcpTools(tables, customOperations) {
316
316
  if (hasValidPrimaryKey(table)) {
317
317
  tools.push({
318
318
  name: `hooks_update_${lcFirst(singularName)}_mutation`,
319
- description: `React Query mutation hook to update a ${singularName}`,
319
+ description: table.description || `React Query mutation hook to update a ${singularName}`,
320
320
  inputSchema: {
321
321
  type: 'object',
322
322
  properties: {
@@ -330,7 +330,7 @@ export function getHooksMcpTools(tables, customOperations) {
330
330
  });
331
331
  tools.push({
332
332
  name: `hooks_delete_${lcFirst(singularName)}_mutation`,
333
- description: `React Query mutation hook to delete a ${singularName}`,
333
+ description: table.description || `React Query mutation hook to delete a ${singularName}`,
334
334
  inputSchema: {
335
335
  type: 'object',
336
336
  properties: {
@@ -384,7 +384,7 @@ export function generateHooksSkills(tables, customOperations) {
384
384
  fileName: `skills/${lcFirst(singularName)}.md`,
385
385
  content: buildSkillFile({
386
386
  name: `hooks-${lcFirst(singularName)}`,
387
- description: `React Query hooks for ${table.name} data operations`,
387
+ description: table.description || `React Query hooks for ${table.name} data operations`,
388
388
  language: 'typescript',
389
389
  usage: [
390
390
  `${getListQueryHookName(table)}({ selection: { fields: { ${selectFields} } } })`,
@@ -76,7 +76,7 @@ export function generateCreateMutationHook(table, options = {}) {
76
76
  ]);
77
77
  const o1 = exportDeclareFunction(hookName, createSTypeParam(selectTypeName), [createFunctionParam('params', o1ParamType)], useMutationResultType(resultType(sRef()), createVarType));
78
78
  addJSDocComment(o1, [
79
- `Mutation hook for creating a ${typeName}`,
79
+ table.description || `Mutation hook for creating a ${typeName}`,
80
80
  '',
81
81
  '@example',
82
82
  '```tsx',
@@ -129,7 +129,7 @@ export function generateCreateMutationHook(table, options = {}) {
129
129
  statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
130
130
  return {
131
131
  fileName: getCreateMutationFileName(table),
132
- content: generateHookFileCode(`Create mutation hook for ${typeName}`, statements),
132
+ content: generateHookFileCode(table.description || `Create mutation hook for ${typeName}`, statements),
133
133
  };
134
134
  }
135
135
  export function generateUpdateMutationHook(table, options = {}) {
@@ -185,7 +185,7 @@ export function generateUpdateMutationHook(table, options = {}) {
185
185
  ]);
186
186
  const o1 = exportDeclareFunction(hookName, createSTypeParam(selectTypeName), [createFunctionParam('params', o1ParamType)], useMutationResultType(resultType(sRef()), updateVarType));
187
187
  addJSDocComment(o1, [
188
- `Mutation hook for updating a ${typeName}`,
188
+ table.description || `Mutation hook for updating a ${typeName}`,
189
189
  '',
190
190
  '@example',
191
191
  '```tsx',
@@ -256,7 +256,7 @@ export function generateUpdateMutationHook(table, options = {}) {
256
256
  statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
257
257
  return {
258
258
  fileName: getUpdateMutationFileName(table),
259
- content: generateHookFileCode(`Update mutation hook for ${typeName}`, statements),
259
+ content: generateHookFileCode(table.description || `Update mutation hook for ${typeName}`, statements),
260
260
  };
261
261
  }
262
262
  export function generateDeleteMutationHook(table, options = {}) {
@@ -309,7 +309,7 @@ export function generateDeleteMutationHook(table, options = {}) {
309
309
  ]);
310
310
  const o1 = exportDeclareFunction(hookName, createSTypeParam(selectTypeName), [createFunctionParam('params', o1ParamType)], useMutationResultType(resultType(sRef()), deleteVarType));
311
311
  addJSDocComment(o1, [
312
- `Mutation hook for deleting a ${typeName} with typed selection`,
312
+ table.description || `Mutation hook for deleting a ${typeName} with typed selection`,
313
313
  '',
314
314
  '@example',
315
315
  '```tsx',
@@ -374,7 +374,7 @@ export function generateDeleteMutationHook(table, options = {}) {
374
374
  statements.push(exportFunction(hookName, null, [createFunctionParam('params', implParamType)], body));
375
375
  return {
376
376
  fileName: getDeleteMutationFileName(table),
377
- content: generateHookFileCode(`Delete mutation hook for ${typeName}`, statements),
377
+ content: generateHookFileCode(table.description || `Delete mutation hook for ${typeName}`, statements),
378
378
  };
379
379
  }
380
380
  export function generateAllMutationHooks(tables, options = {}) {
@@ -6,8 +6,8 @@ export interface GeneratedCustomOpsFile {
6
6
  /**
7
7
  * Generate the query/index.ts file for custom query operations
8
8
  */
9
- export declare function generateCustomQueryOpsFile(operations: CleanOperation[]): GeneratedCustomOpsFile;
9
+ export declare function generateCustomQueryOpsFile(operations: CleanOperation[], comments?: boolean): GeneratedCustomOpsFile;
10
10
  /**
11
11
  * Generate the mutation/index.ts file for custom mutation operations
12
12
  */
13
- export declare function generateCustomMutationOpsFile(operations: CleanOperation[]): GeneratedCustomOpsFile;
13
+ export declare function generateCustomMutationOpsFile(operations: CleanOperation[], comments?: boolean): GeneratedCustomOpsFile;
@@ -5,10 +5,10 @@
5
5
  * like login, register, currentUser, etc.
6
6
  */
7
7
  import * as t from '@babel/types';
8
- import { generateCode } from '../babel-ast';
8
+ import { addJSDocComment, generateCode } from '../babel-ast';
9
9
  import { NON_SELECT_TYPES, getSelectTypeName } from '../select-helpers';
10
10
  import { getTypeBaseName, isTypeRequired, scalarToTsType, typeRefToTsType, } from '../type-resolver';
11
- import { getGeneratedFileHeader, ucFirst } from '../utils';
11
+ import { getGeneratedFileHeader, stripSmartComments, ucFirst } from '../utils';
12
12
  /**
13
13
  * Collect all input type names used by operations
14
14
  * Includes Input, Filter, OrderBy, and Condition types
@@ -67,7 +67,7 @@ function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false
67
67
  decl.importKind = typeOnly ? 'type' : 'value';
68
68
  return decl;
69
69
  }
70
- function createVariablesInterface(op) {
70
+ function createVariablesInterface(op, comments = true) {
71
71
  if (op.args.length === 0)
72
72
  return null;
73
73
  const varTypeName = `${ucFirst(op.name)}Variables`;
@@ -75,10 +75,19 @@ function createVariablesInterface(op) {
75
75
  const optional = !isTypeRequired(arg.type);
76
76
  const prop = t.tsPropertySignature(t.identifier(arg.name), t.tsTypeAnnotation(parseTypeAnnotation(typeRefToTsType(arg.type))));
77
77
  prop.optional = optional;
78
+ const argDescription = stripSmartComments(arg.description, comments);
79
+ if (argDescription) {
80
+ addJSDocComment(prop, argDescription.split('\n'));
81
+ }
78
82
  return prop;
79
83
  });
80
84
  const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(varTypeName), null, null, t.tsInterfaceBody(props));
81
- return t.exportNamedDeclaration(interfaceDecl);
85
+ const exportDecl = t.exportNamedDeclaration(interfaceDecl);
86
+ const opDescription = stripSmartComments(op.description, comments);
87
+ if (opDescription) {
88
+ addJSDocComment(exportDecl, [`Variables for ${op.name}`, opDescription]);
89
+ }
90
+ return exportDecl;
82
91
  }
83
92
  function parseTypeAnnotation(typeStr) {
84
93
  if (typeStr === 'string')
@@ -250,7 +259,7 @@ function buildOperationMethod(op, operationType) {
250
259
  /**
251
260
  * Generate the query/index.ts file for custom query operations
252
261
  */
253
- export function generateCustomQueryOpsFile(operations) {
262
+ export function generateCustomQueryOpsFile(operations, comments = true) {
254
263
  const statements = [];
255
264
  // Collect all input type names and payload type names
256
265
  const inputTypeNames = collectInputTypeNamesFromOps(operations);
@@ -278,7 +287,7 @@ export function generateCustomQueryOpsFile(operations) {
278
287
  statements.push(createImportDeclaration('../input-types', ['connectionFieldsMap']));
279
288
  // Generate variable interfaces
280
289
  for (const op of operations) {
281
- const varInterface = createVariablesInterface(op);
290
+ const varInterface = createVariablesInterface(op, comments);
282
291
  if (varInterface)
283
292
  statements.push(varInterface);
284
293
  }
@@ -300,7 +309,7 @@ export function generateCustomQueryOpsFile(operations) {
300
309
  /**
301
310
  * Generate the mutation/index.ts file for custom mutation operations
302
311
  */
303
- export function generateCustomMutationOpsFile(operations) {
312
+ export function generateCustomMutationOpsFile(operations, comments = true) {
304
313
  const statements = [];
305
314
  // Collect all input type names and payload type names
306
315
  const inputTypeNames = collectInputTypeNamesFromOps(operations);
@@ -328,7 +337,7 @@ export function generateCustomMutationOpsFile(operations) {
328
337
  statements.push(createImportDeclaration('../input-types', ['connectionFieldsMap']));
329
338
  // Generate variable interfaces
330
339
  for (const op of operations) {
331
- const varInterface = createVariablesInterface(op);
340
+ const varInterface = createVariablesInterface(op, comments);
332
341
  if (varInterface)
333
342
  statements.push(varInterface);
334
343
  }