@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
@@ -224,7 +224,7 @@ export function getOrmMcpTools(tables, customOperations) {
224
224
  const editableFields = getEditableFields(table);
225
225
  tools.push({
226
226
  name: `orm_${lcFirst(singularName)}_findMany`,
227
- description: `List all ${table.name} records via ORM`,
227
+ description: table.description || `List all ${table.name} records via ORM`,
228
228
  inputSchema: {
229
229
  type: 'object',
230
230
  properties: {
@@ -241,7 +241,7 @@ export function getOrmMcpTools(tables, customOperations) {
241
241
  });
242
242
  tools.push({
243
243
  name: `orm_${lcFirst(singularName)}_findOne`,
244
- description: `Get a single ${table.name} record by ${pk.name}`,
244
+ description: table.description || `Get a single ${table.name} record by ${pk.name}`,
245
245
  inputSchema: {
246
246
  type: 'object',
247
247
  properties: {
@@ -262,7 +262,7 @@ export function getOrmMcpTools(tables, customOperations) {
262
262
  }
263
263
  tools.push({
264
264
  name: `orm_${lcFirst(singularName)}_create`,
265
- description: `Create a new ${table.name} record`,
265
+ description: table.description || `Create a new ${table.name} record`,
266
266
  inputSchema: {
267
267
  type: 'object',
268
268
  properties: createProps,
@@ -283,7 +283,7 @@ export function getOrmMcpTools(tables, customOperations) {
283
283
  }
284
284
  tools.push({
285
285
  name: `orm_${lcFirst(singularName)}_update`,
286
- description: `Update an existing ${table.name} record`,
286
+ description: table.description || `Update an existing ${table.name} record`,
287
287
  inputSchema: {
288
288
  type: 'object',
289
289
  properties: updateProps,
@@ -292,7 +292,7 @@ export function getOrmMcpTools(tables, customOperations) {
292
292
  });
293
293
  tools.push({
294
294
  name: `orm_${lcFirst(singularName)}_delete`,
295
- description: `Delete a ${table.name} record by ${pk.name}`,
295
+ description: table.description || `Delete a ${table.name} record by ${pk.name}`,
296
296
  inputSchema: {
297
297
  type: 'object',
298
298
  properties: {
@@ -350,7 +350,7 @@ export function generateOrmSkills(tables, customOperations) {
350
350
  fileName: `skills/${lcFirst(singularName)}.md`,
351
351
  content: buildSkillFile({
352
352
  name: `orm-${lcFirst(singularName)}`,
353
- description: `ORM operations for ${table.name} records`,
353
+ description: table.description || `ORM operations for ${table.name} records`,
354
354
  language: 'typescript',
355
355
  usage: [
356
356
  `db.${lcFirst(singularName)}.findMany({ select: { id: true } }).execute()`,
@@ -8,6 +8,7 @@ import { generateAllModelFiles } from './model-generator';
8
8
  */
9
9
  export function generateOrm(options) {
10
10
  const { tables, customOperations, sharedTypesPath } = options;
11
+ const commentsEnabled = options.config.codegen?.comments !== false;
11
12
  const files = [];
12
13
  // Use shared types when a sharedTypesPath is provided (unified output mode)
13
14
  const useSharedTypes = !!sharedTypesPath;
@@ -65,7 +66,7 @@ export function generateOrm(options) {
65
66
  }
66
67
  }
67
68
  }
68
- const inputTypesFile = generateInputTypesFile(typeRegistry ?? new Map(), usedInputTypes, tables, usedPayloadTypes);
69
+ const inputTypesFile = generateInputTypesFile(typeRegistry ?? new Map(), usedInputTypes, tables, usedPayloadTypes, commentsEnabled);
69
70
  files.push({
70
71
  path: inputTypesFile.fileName,
71
72
  content: inputTypesFile.content,
@@ -73,11 +74,11 @@ export function generateOrm(options) {
73
74
  }
74
75
  // 5. Generate custom operations (if any)
75
76
  if (hasCustomQueries && customOperations?.queries) {
76
- const queryOpsFile = generateCustomQueryOpsFile(customOperations.queries);
77
+ const queryOpsFile = generateCustomQueryOpsFile(customOperations.queries, commentsEnabled);
77
78
  files.push({ path: queryOpsFile.fileName, content: queryOpsFile.content });
78
79
  }
79
80
  if (hasCustomMutations && customOperations?.mutations) {
80
- const mutationOpsFile = generateCustomMutationOpsFile(customOperations.mutations);
81
+ const mutationOpsFile = generateCustomMutationOpsFile(customOperations.mutations, commentsEnabled);
81
82
  files.push({
82
83
  path: mutationOpsFile.fileName,
83
84
  content: mutationOpsFile.content,
@@ -18,4 +18,4 @@ export declare function collectPayloadTypeNames(operations: Array<{
18
18
  /**
19
19
  * Generate comprehensive input-types.ts file using Babel AST
20
20
  */
21
- export declare function generateInputTypesFile(typeRegistry: TypeRegistry, usedInputTypes: Set<string>, tables?: CleanTable[], usedPayloadTypes?: Set<string>): GeneratedInputTypesFile;
21
+ export declare function generateInputTypesFile(typeRegistry: TypeRegistry, usedInputTypes: Set<string>, tables?: CleanTable[], usedPayloadTypes?: Set<string>, comments?: boolean): GeneratedInputTypesFile;
@@ -12,10 +12,10 @@
12
12
  */
13
13
  import * as t from '@babel/types';
14
14
  import { pluralize } from 'inflekt';
15
- import { addLineComment, generateCode } from '../babel-ast';
15
+ import { addJSDocComment, addLineComment, generateCode } from '../babel-ast';
16
16
  import { SCALAR_NAMES, scalarToFilterType, scalarToTsType } from '../scalars';
17
17
  import { getTypeBaseName } from '../type-resolver';
18
- import { getCreateInputTypeName, getConditionTypeName, getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getTableNames, isRelationField, lcFirst, } from '../utils';
18
+ import { getCreateInputTypeName, getConditionTypeName, getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getTableNames, isRelationField, lcFirst, stripSmartComments, } from '../utils';
19
19
  // ============================================================================
20
20
  // Constants
21
21
  // ============================================================================
@@ -117,19 +117,26 @@ function parseTypeString(typeStr) {
117
117
  /**
118
118
  * Create an interface property signature
119
119
  */
120
- function createPropertySignature(name, typeStr, optional) {
120
+ function createPropertySignature(name, typeStr, optional, description) {
121
121
  const prop = t.tsPropertySignature(t.identifier(name), t.tsTypeAnnotation(parseTypeString(typeStr)));
122
122
  prop.optional = optional;
123
+ if (description) {
124
+ addJSDocComment(prop, description.split('\n'));
125
+ }
123
126
  return prop;
124
127
  }
125
128
  /**
126
129
  * Create an exported interface declaration
127
130
  */
128
- function createExportedInterface(name, properties) {
129
- const props = properties.map((p) => createPropertySignature(p.name, p.type, p.optional));
131
+ function createExportedInterface(name, properties, description) {
132
+ const props = properties.map((p) => createPropertySignature(p.name, p.type, p.optional, p.description));
130
133
  const body = t.tsInterfaceBody(props);
131
134
  const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(name), null, null, body);
132
- return t.exportNamedDeclaration(interfaceDecl);
135
+ const exportDecl = t.exportNamedDeclaration(interfaceDecl);
136
+ if (description) {
137
+ addJSDocComment(exportDecl, description.split('\n'));
138
+ }
139
+ return exportDecl;
133
140
  }
134
141
  /**
135
142
  * Create an exported type alias declaration
@@ -310,7 +317,7 @@ function collectEnumTypesFromTables(tables, typeRegistry) {
310
317
  /**
311
318
  * Generate enum type statements
312
319
  */
313
- function generateEnumTypes(typeRegistry, enumTypeNames) {
320
+ function generateEnumTypes(typeRegistry, enumTypeNames, comments = true) {
314
321
  if (enumTypeNames.size === 0)
315
322
  return [];
316
323
  const statements = [];
@@ -320,7 +327,12 @@ function generateEnumTypes(typeRegistry, enumTypeNames) {
320
327
  continue;
321
328
  const unionType = createStringLiteralUnion(typeInfo.enumValues);
322
329
  const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
323
- statements.push(t.exportNamedDeclaration(typeAlias));
330
+ const exportDecl = t.exportNamedDeclaration(typeAlias);
331
+ const enumDescription = stripSmartComments(typeInfo.description, comments);
332
+ if (enumDescription) {
333
+ addJSDocComment(exportDecl, enumDescription.split('\n'));
334
+ }
335
+ statements.push(exportDecl);
324
336
  }
325
337
  if (statements.length > 0) {
326
338
  addSectionComment(statements, 'Enum Types');
@@ -380,6 +392,7 @@ function buildEntityProperties(table) {
380
392
  name: field.name,
381
393
  type: isNullable ? `${tsType} | null` : tsType,
382
394
  optional: isNullable,
395
+ description: field.description,
383
396
  });
384
397
  }
385
398
  return properties;
@@ -391,7 +404,7 @@ function generateEntityTypes(tables) {
391
404
  const statements = [];
392
405
  for (const table of tables) {
393
406
  const { typeName } = getTableNames(table);
394
- statements.push(createExportedInterface(typeName, buildEntityProperties(table)));
407
+ statements.push(createExportedInterface(typeName, buildEntityProperties(table), table.description));
395
408
  }
396
409
  if (statements.length > 0) {
397
410
  addSectionComment(statements, 'Entity Types');
@@ -962,7 +975,7 @@ function buildTableCrudTypeNames(tables) {
962
975
  /**
963
976
  * Generate custom input type statements from TypeRegistry
964
977
  */
965
- function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes) {
978
+ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes, comments = true) {
966
979
  const statements = [];
967
980
  const generatedTypes = new Set();
968
981
  const typesToGenerate = new Set(Array.from(usedInputTypes));
@@ -998,7 +1011,12 @@ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes)
998
1011
  for (const field of typeInfo.inputFields) {
999
1012
  const optional = !isRequired(field.type);
1000
1013
  const tsType = typeRefToTs(field.type);
1001
- properties.push({ name: field.name, type: tsType, optional });
1014
+ properties.push({
1015
+ name: field.name,
1016
+ type: tsType,
1017
+ optional,
1018
+ description: stripSmartComments(field.description, comments),
1019
+ });
1002
1020
  // Follow nested Input types
1003
1021
  const baseType = getTypeBaseName(field.type);
1004
1022
  if (baseType &&
@@ -1007,12 +1025,17 @@ function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes)
1007
1025
  typesToGenerate.add(baseType);
1008
1026
  }
1009
1027
  }
1010
- statements.push(createExportedInterface(typeName, properties));
1028
+ statements.push(createExportedInterface(typeName, properties, stripSmartComments(typeInfo.description, comments)));
1011
1029
  }
1012
1030
  else if (typeInfo.kind === 'ENUM' && typeInfo.enumValues) {
1013
1031
  const unionType = createStringLiteralUnion(typeInfo.enumValues);
1014
1032
  const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
1015
- statements.push(t.exportNamedDeclaration(typeAlias));
1033
+ const enumExportDecl = t.exportNamedDeclaration(typeAlias);
1034
+ const enumDescription = stripSmartComments(typeInfo.description, comments);
1035
+ if (enumDescription) {
1036
+ addJSDocComment(enumExportDecl, enumDescription.split('\n'));
1037
+ }
1038
+ statements.push(enumExportDecl);
1016
1039
  }
1017
1040
  else {
1018
1041
  // Add comment for unsupported type kind
@@ -1046,7 +1069,7 @@ export function collectPayloadTypeNames(operations) {
1046
1069
  /**
1047
1070
  * Generate payload/return type statements
1048
1071
  */
1049
- function generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes) {
1072
+ function generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes, comments = true) {
1050
1073
  const statements = [];
1051
1074
  const generatedTypes = new Set(alreadyGeneratedTypes);
1052
1075
  const typesToGenerate = new Set(Array.from(usedPayloadTypes));
@@ -1094,6 +1117,7 @@ function generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTy
1094
1117
  name: field.name,
1095
1118
  type: isNullable ? `${tsType} | null` : tsType,
1096
1119
  optional: isNullable,
1120
+ description: stripSmartComments(field.description, comments),
1097
1121
  });
1098
1122
  // Follow nested OBJECT types
1099
1123
  if (baseType &&
@@ -1105,7 +1129,7 @@ function generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTy
1105
1129
  }
1106
1130
  }
1107
1131
  }
1108
- statements.push(createExportedInterface(typeName, interfaceProps));
1132
+ statements.push(createExportedInterface(typeName, interfaceProps, stripSmartComments(typeInfo.description, comments)));
1109
1133
  // Build Select type
1110
1134
  const selectMembers = [];
1111
1135
  for (const field of typeInfo.fields) {
@@ -1186,7 +1210,7 @@ function generateConnectionFieldsMap(tables, tableByName) {
1186
1210
  /**
1187
1211
  * Generate comprehensive input-types.ts file using Babel AST
1188
1212
  */
1189
- export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes) {
1213
+ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes, comments = true) {
1190
1214
  const statements = [];
1191
1215
  const tablesList = tables ?? [];
1192
1216
  const hasTables = tablesList.length > 0;
@@ -1198,7 +1222,7 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
1198
1222
  // 1. Scalar filter types
1199
1223
  statements.push(...generateScalarFilterTypes());
1200
1224
  // 2. Enum types used by table fields
1201
- statements.push(...generateEnumTypes(typeRegistry, enumTypes));
1225
+ statements.push(...generateEnumTypes(typeRegistry, enumTypes, comments));
1202
1226
  // 2b. Unknown/custom scalar aliases for schema-specific scalars
1203
1227
  statements.push(...generateCustomScalarTypes(customScalarTypes));
1204
1228
  // 3. Entity and relation types (if tables provided)
@@ -1222,7 +1246,7 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
1222
1246
  statements.push(...generateConnectionFieldsMap(tablesList, tableByName));
1223
1247
  // 7. Custom input types from TypeRegistry
1224
1248
  const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
1225
- statements.push(...generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes));
1249
+ statements.push(...generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes, comments));
1226
1250
  // 8. Payload/return types for custom operations
1227
1251
  if (usedPayloadTypes && usedPayloadTypes.size > 0) {
1228
1252
  const alreadyGeneratedTypes = new Set();
@@ -1232,7 +1256,7 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
1232
1256
  alreadyGeneratedTypes.add(typeName);
1233
1257
  }
1234
1258
  }
1235
- statements.push(...generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes));
1259
+ statements.push(...generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes, comments));
1236
1260
  }
1237
1261
  // Generate code with file header
1238
1262
  const header = getGeneratedFileHeader('GraphQL types for ORM client');
@@ -94,8 +94,9 @@ export function generateListQueryHook(table, options = {}) {
94
94
  };
95
95
  // Hook
96
96
  if (reactQueryEnabled) {
97
+ const descLine = table.description || `Query hook for fetching ${typeName} list`;
97
98
  const docLines = [
98
- `Query hook for fetching ${typeName} list`,
99
+ descLine,
99
100
  '',
100
101
  '@example',
101
102
  '```tsx',
@@ -167,7 +168,7 @@ export function generateListQueryHook(table, options = {}) {
167
168
  ]);
168
169
  const f1Decl = exportAsyncDeclareFunction(fetchFnName, createSTypeParam(selectTypeName), [createFunctionParam('params', f1ParamType)], typeRef('Promise', [listResultTypeAST(sRef())]));
169
170
  addJSDocComment(f1Decl, [
170
- `Fetch ${typeName} list without React hooks`,
171
+ table.description || `Fetch ${typeName} list without React hooks`,
171
172
  '',
172
173
  '@example',
173
174
  '```ts',
@@ -209,7 +210,7 @@ export function generateListQueryHook(table, options = {}) {
209
210
  createFunctionParam('params', p1ParamType),
210
211
  ], typeRef('Promise', [t.tsVoidKeyword()]));
211
212
  addJSDocComment(p1Decl, [
212
- `Prefetch ${typeName} list for SSR or cache warming`,
213
+ table.description || `Prefetch ${typeName} list for SSR or cache warming`,
213
214
  '',
214
215
  '@example',
215
216
  '```ts',
@@ -245,9 +246,9 @@ export function generateListQueryHook(table, options = {}) {
245
246
  createFunctionParam('params', pImplParamType),
246
247
  ], pBody, typeRef('Promise', [t.tsVoidKeyword()])));
247
248
  }
248
- const headerText = reactQueryEnabled
249
+ const headerText = table.description || (reactQueryEnabled
249
250
  ? `List query hook for ${typeName}`
250
- : `List query functions for ${typeName}`;
251
+ : `List query functions for ${typeName}`);
251
252
  return {
252
253
  fileName: getListQueryFileName(table),
253
254
  content: generateHookFileCode(headerText, statements),
@@ -331,8 +332,9 @@ export function generateSingleQueryHook(table, options = {}) {
331
332
  };
332
333
  // Hook
333
334
  if (reactQueryEnabled) {
335
+ const singleDescLine = table.description || `Query hook for fetching a single ${typeName}`;
334
336
  const docLines = [
335
- `Query hook for fetching a single ${typeName}`,
337
+ singleDescLine,
336
338
  '',
337
339
  '@example',
338
340
  '```tsx',
@@ -409,7 +411,7 @@ export function generateSingleQueryHook(table, options = {}) {
409
411
  ]);
410
412
  const f1Decl = exportAsyncDeclareFunction(fetchFnName, createSTypeParam(selectTypeName), [createFunctionParam('params', f1ParamType)], typeRef('Promise', [singleResultTypeAST(sRef())]));
411
413
  addJSDocComment(f1Decl, [
412
- `Fetch a single ${typeName} without React hooks`,
414
+ table.description || `Fetch a single ${typeName} without React hooks`,
413
415
  '',
414
416
  '@example',
415
417
  '```ts',
@@ -451,7 +453,7 @@ export function generateSingleQueryHook(table, options = {}) {
451
453
  createFunctionParam('params', p1ParamType),
452
454
  ], typeRef('Promise', [t.tsVoidKeyword()]));
453
455
  addJSDocComment(p1Decl, [
454
- `Prefetch a single ${typeName} for SSR or cache warming`,
456
+ table.description || `Prefetch a single ${typeName} for SSR or cache warming`,
455
457
  '',
456
458
  '@example',
457
459
  '```ts',
@@ -489,9 +491,9 @@ export function generateSingleQueryHook(table, options = {}) {
489
491
  createFunctionParam('params', pImplParamType),
490
492
  ], pBody, typeRef('Promise', [t.tsVoidKeyword()])));
491
493
  }
492
- const headerText = reactQueryEnabled
494
+ const headerText = table.description || (reactQueryEnabled
493
495
  ? `Single item query hook for ${typeName}`
494
- : `Single item query functions for ${typeName}`;
496
+ : `Single item query functions for ${typeName}`);
495
497
  return {
496
498
  fileName: getSingleQueryFileName(table),
497
499
  content: generateHookFileCode(headerText, statements),
@@ -22,6 +22,8 @@ export interface GeneratedSchemaTypesFile {
22
22
  export interface GenerateSchemaTypesOptions {
23
23
  typeRegistry: TypeRegistry;
24
24
  tableTypeNames: Set<string>;
25
+ /** Include descriptions as JSDoc comments. @default true */
26
+ comments?: boolean;
25
27
  }
26
28
  export interface PayloadTypesResult {
27
29
  statements: t.Statement[];
@@ -12,10 +12,10 @@
12
12
  * Uses Babel AST for robust code generation.
13
13
  */
14
14
  import * as t from '@babel/types';
15
- import { generateCode } from './babel-ast';
15
+ import { addJSDocComment, generateCode } from './babel-ast';
16
16
  import { BASE_FILTER_TYPE_NAMES, SCALAR_NAMES, scalarToTsType, } from './scalars';
17
17
  import { getTypeBaseName } from './type-resolver';
18
- import { getGeneratedFileHeader } from './utils';
18
+ import { getGeneratedFileHeader, stripSmartComments } from './utils';
19
19
  const SKIP_TYPES = new Set([
20
20
  ...SCALAR_NAMES,
21
21
  'Query',
@@ -79,7 +79,7 @@ function generateCustomScalarTypes(customScalarTypes) {
79
79
  }
80
80
  return statements;
81
81
  }
82
- function generateEnumTypes(typeRegistry, tableTypeNames) {
82
+ function generateEnumTypes(typeRegistry, tableTypeNames, comments = true) {
83
83
  const statements = [];
84
84
  const generatedTypes = new Set();
85
85
  for (const [typeName, typeInfo] of typeRegistry) {
@@ -91,12 +91,17 @@ function generateEnumTypes(typeRegistry, tableTypeNames) {
91
91
  continue;
92
92
  const unionType = t.tsUnionType(typeInfo.enumValues.map((v) => t.tsLiteralType(t.stringLiteral(v))));
93
93
  const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
94
- statements.push(t.exportNamedDeclaration(typeAlias));
94
+ const exportDecl = t.exportNamedDeclaration(typeAlias);
95
+ const enumDescription = stripSmartComments(typeInfo.description, comments);
96
+ if (enumDescription) {
97
+ addJSDocComment(exportDecl, enumDescription.split('\n'));
98
+ }
99
+ statements.push(exportDecl);
95
100
  generatedTypes.add(typeName);
96
101
  }
97
102
  return { statements, generatedTypes };
98
103
  }
99
- function generateInputObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated) {
104
+ function generateInputObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated, comments = true) {
100
105
  const statements = [];
101
106
  const generatedTypes = new Set(alreadyGenerated);
102
107
  const typesToGenerate = new Set();
@@ -128,6 +133,10 @@ function generateInputObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated
128
133
  const tsType = typeRefToTs(field.type);
129
134
  const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(tsType))));
130
135
  prop.optional = optional;
136
+ const fieldDescription = stripSmartComments(field.description, comments);
137
+ if (fieldDescription) {
138
+ addJSDocComment(prop, fieldDescription.split('\n'));
139
+ }
131
140
  properties.push(prop);
132
141
  const baseType = getTypeBaseName(field.type);
133
142
  if (baseType &&
@@ -141,11 +150,16 @@ function generateInputObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated
141
150
  }
142
151
  }
143
152
  const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(typeName), null, null, t.tsInterfaceBody(properties));
144
- statements.push(t.exportNamedDeclaration(interfaceDecl));
153
+ const exportDecl = t.exportNamedDeclaration(interfaceDecl);
154
+ const inputDescription = stripSmartComments(typeInfo.description, comments);
155
+ if (inputDescription) {
156
+ addJSDocComment(exportDecl, inputDescription.split('\n'));
157
+ }
158
+ statements.push(exportDecl);
145
159
  }
146
160
  return { statements, generatedTypes };
147
161
  }
148
- function generateUnionTypes(typeRegistry, tableTypeNames, alreadyGenerated) {
162
+ function generateUnionTypes(typeRegistry, tableTypeNames, alreadyGenerated, comments = true) {
149
163
  const statements = [];
150
164
  const generatedTypes = new Set(alreadyGenerated);
151
165
  for (const [typeName, typeInfo] of typeRegistry) {
@@ -159,7 +173,12 @@ function generateUnionTypes(typeRegistry, tableTypeNames, alreadyGenerated) {
159
173
  continue;
160
174
  const unionType = t.tsUnionType(typeInfo.possibleTypes.map((pt) => t.tsTypeReference(t.identifier(pt))));
161
175
  const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
162
- statements.push(t.exportNamedDeclaration(typeAlias));
176
+ const exportDecl = t.exportNamedDeclaration(typeAlias);
177
+ const unionDescription = stripSmartComments(typeInfo.description, comments);
178
+ if (unionDescription) {
179
+ addJSDocComment(exportDecl, unionDescription.split('\n'));
180
+ }
181
+ statements.push(exportDecl);
163
182
  generatedTypes.add(typeName);
164
183
  }
165
184
  return { statements, generatedTypes };
@@ -187,7 +206,7 @@ function collectReturnTypesFromRootTypes(typeRegistry, tableTypeNames) {
187
206
  processFields(mutationType.fields);
188
207
  return returnTypes;
189
208
  }
190
- function generatePayloadObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated) {
209
+ function generatePayloadObjectTypes(typeRegistry, tableTypeNames, alreadyGenerated, comments = true) {
191
210
  const statements = [];
192
211
  const generatedTypes = new Set(alreadyGenerated);
193
212
  const referencedTableTypes = new Set();
@@ -220,6 +239,10 @@ function generatePayloadObjectTypes(typeRegistry, tableTypeNames, alreadyGenerat
220
239
  const finalType = isNullable ? `${tsType} | null` : tsType;
221
240
  const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(finalType))));
222
241
  prop.optional = isNullable;
242
+ const fieldDescription = stripSmartComments(field.description, comments);
243
+ if (fieldDescription) {
244
+ addJSDocComment(prop, fieldDescription.split('\n'));
245
+ }
223
246
  properties.push(prop);
224
247
  if (baseType && tableTypeNames.has(baseType)) {
225
248
  referencedTableTypes.add(baseType);
@@ -235,22 +258,28 @@ function generatePayloadObjectTypes(typeRegistry, tableTypeNames, alreadyGenerat
235
258
  }
236
259
  }
237
260
  const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(typeName), null, null, t.tsInterfaceBody(properties));
238
- statements.push(t.exportNamedDeclaration(interfaceDecl));
261
+ const exportDecl = t.exportNamedDeclaration(interfaceDecl);
262
+ const payloadDescription = stripSmartComments(typeInfo.description, comments);
263
+ if (payloadDescription) {
264
+ addJSDocComment(exportDecl, payloadDescription.split('\n'));
265
+ }
266
+ statements.push(exportDecl);
239
267
  }
240
268
  return { statements, generatedTypes, referencedTableTypes };
241
269
  }
242
270
  export function generateSchemaTypesFile(options) {
243
271
  const { typeRegistry, tableTypeNames } = options;
272
+ const comments = options.comments !== false;
244
273
  const allStatements = [];
245
274
  let generatedTypes = new Set();
246
275
  const customScalarTypes = collectCustomScalarTypes(typeRegistry);
247
- const enumResult = generateEnumTypes(typeRegistry, tableTypeNames);
276
+ const enumResult = generateEnumTypes(typeRegistry, tableTypeNames, comments);
248
277
  generatedTypes = new Set([...generatedTypes, ...enumResult.generatedTypes]);
249
- const unionResult = generateUnionTypes(typeRegistry, tableTypeNames, generatedTypes);
278
+ const unionResult = generateUnionTypes(typeRegistry, tableTypeNames, generatedTypes, comments);
250
279
  generatedTypes = new Set([...generatedTypes, ...unionResult.generatedTypes]);
251
- const inputResult = generateInputObjectTypes(typeRegistry, tableTypeNames, generatedTypes);
280
+ const inputResult = generateInputObjectTypes(typeRegistry, tableTypeNames, generatedTypes, comments);
252
281
  generatedTypes = new Set([...generatedTypes, ...inputResult.generatedTypes]);
253
- const payloadResult = generatePayloadObjectTypes(typeRegistry, tableTypeNames, generatedTypes);
282
+ const payloadResult = generatePayloadObjectTypes(typeRegistry, tableTypeNames, generatedTypes, comments);
254
283
  const referencedTableTypes = Array.from(payloadResult.referencedTableTypes).sort();
255
284
  const baseFilterImports = Array.from(BASE_FILTER_TYPE_NAMES).sort();
256
285
  const allTypesImports = [...referencedTableTypes, ...baseFilterImports];
@@ -27,6 +27,7 @@ function exportAllFrom(modulePath) {
27
27
  */
28
28
  export function generateSharedTypes(options) {
29
29
  const { tables, customOperations } = options;
30
+ const commentsEnabled = options.config.codegen?.comments !== false;
30
31
  const files = [];
31
32
  // Collect table type names for import path resolution
32
33
  const tableTypeNames = new Set(tables.map((t) => getTableNames(t).typeName));
@@ -38,6 +39,7 @@ export function generateSharedTypes(options) {
38
39
  const schemaTypesResult = generateSchemaTypesFile({
39
40
  typeRegistry: customOperations.typeRegistry,
40
41
  tableTypeNames,
42
+ comments: commentsEnabled,
41
43
  });
42
44
  // Only include if there's meaningful content
43
45
  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
  */
@@ -323,6 +323,92 @@ export function getQueryKeyPrefix(table) {
323
323
  return lcFirst(table.name);
324
324
  }
325
325
  // ============================================================================
326
+ // Smart Comment Utilities
327
+ // ============================================================================
328
+ /**
329
+ * PostGraphile smart comment tags that should be stripped from descriptions.
330
+ * Smart comments start with `@` and control PostGraphile behavior
331
+ * (e.g., `@omit`, `@name`, `@foreignKey`, etc.)
332
+ *
333
+ * A PostgreSQL COMMENT may contain both human-readable text and smart comments:
334
+ * COMMENT ON TABLE users IS 'User accounts for the application\n@omit delete';
335
+ *
336
+ * PostGraphile's introspection already separates these: the GraphQL `description`
337
+ * field contains only the human-readable part. So in most cases, the description
338
+ * we receive from introspection is already clean.
339
+ *
340
+ * However, as a safety measure, this utility strips any remaining `@`-prefixed
341
+ * lines that may have leaked through.
342
+ */
343
+ /**
344
+ * PostGraphile auto-generated boilerplate descriptions that add no value.
345
+ * These are generic descriptions PostGraphile puts on every mutation input,
346
+ * clientMutationId field, etc. We filter them out to keep generated code clean.
347
+ */
348
+ const POSTGRAPHILE_BOILERPLATE = [
349
+ 'The exclusive input argument for this mutation.',
350
+ 'An arbitrary string value with no semantic meaning.',
351
+ 'The exact same `clientMutationId` that was provided in the mutation input,',
352
+ 'The output of our',
353
+ 'All input for the',
354
+ 'A cursor for use in pagination.',
355
+ 'An edge for our',
356
+ 'Information to aid in pagination.',
357
+ 'Reads and enables pagination through a set of',
358
+ 'A list of edges which contains the',
359
+ 'The count of *all* `',
360
+ 'A list of `',
361
+ 'Our root query field',
362
+ 'Reads a single',
363
+ 'The root query type',
364
+ 'The root mutation type',
365
+ ];
366
+ /**
367
+ * Check if a description is generic PostGraphile boilerplate that should be suppressed.
368
+ */
369
+ function isBoilerplateDescription(description) {
370
+ const trimmed = description.trim();
371
+ return POSTGRAPHILE_BOILERPLATE.some((bp) => trimmed.startsWith(bp));
372
+ }
373
+ /**
374
+ * Strip PostGraphile smart comments and boilerplate from a description string.
375
+ *
376
+ * Smart comments are lines starting with `@` (e.g., `@omit`, `@name newName`).
377
+ * Boilerplate descriptions are generic PostGraphile-generated text that repeats
378
+ * on every mutation input, clientMutationId field, etc.
379
+ *
380
+ * This returns only the meaningful human-readable portion of the comment,
381
+ * or undefined if the result is empty or boilerplate.
382
+ *
383
+ * @param description - Raw description from GraphQL introspection
384
+ * @returns Cleaned description, or undefined if empty/boilerplate
385
+ */
386
+ export function stripSmartComments(description, enabled = true) {
387
+ if (!enabled)
388
+ return undefined;
389
+ if (!description)
390
+ return undefined;
391
+ // Check if entire description is boilerplate
392
+ if (isBoilerplateDescription(description))
393
+ return undefined;
394
+ const lines = description.split('\n');
395
+ const cleanLines = [];
396
+ for (const line of lines) {
397
+ const trimmed = line.trim();
398
+ // Skip lines that start with @ (smart comment directives)
399
+ if (trimmed.startsWith('@'))
400
+ continue;
401
+ cleanLines.push(line);
402
+ }
403
+ const result = cleanLines.join('\n').trim();
404
+ if (result.length === 0)
405
+ return undefined;
406
+ // Re-check after stripping smart comments
407
+ if (isBoilerplateDescription(result))
408
+ return undefined;
409
+ return result;
410
+ }
411
+ // ============================================================================
326
412
  // Code generation helpers
327
413
  // ============================================================================
328
414
  /**
@@ -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