@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.
- package/core/codegen/hooks-docs-generator.js +16 -16
- package/core/codegen/mutations.js +6 -6
- package/core/codegen/orm/custom-ops-generator.d.ts +2 -2
- package/core/codegen/orm/custom-ops-generator.js +15 -6
- package/core/codegen/orm/docs-generator.js +6 -6
- package/core/codegen/orm/index.js +4 -3
- package/core/codegen/orm/input-types-generator.d.ts +1 -1
- package/core/codegen/orm/input-types-generator.js +41 -17
- package/core/codegen/queries.js +12 -10
- package/core/codegen/schema-types-generator.d.ts +2 -0
- package/core/codegen/schema-types-generator.js +41 -12
- package/core/codegen/shared/index.js +2 -0
- package/core/codegen/utils.d.ts +14 -0
- package/core/codegen/utils.js +87 -0
- package/core/introspect/infer-tables.d.ts +5 -0
- package/core/introspect/infer-tables.js +11 -4
- package/core/pipeline/index.js +2 -1
- package/esm/core/codegen/hooks-docs-generator.js +16 -16
- package/esm/core/codegen/mutations.js +6 -6
- package/esm/core/codegen/orm/custom-ops-generator.d.ts +2 -2
- package/esm/core/codegen/orm/custom-ops-generator.js +17 -8
- package/esm/core/codegen/orm/docs-generator.js +6 -6
- package/esm/core/codegen/orm/index.js +4 -3
- package/esm/core/codegen/orm/input-types-generator.d.ts +1 -1
- package/esm/core/codegen/orm/input-types-generator.js +43 -19
- package/esm/core/codegen/queries.js +12 -10
- package/esm/core/codegen/schema-types-generator.d.ts +2 -0
- package/esm/core/codegen/schema-types-generator.js +43 -14
- package/esm/core/codegen/shared/index.js +2 -0
- package/esm/core/codegen/utils.d.ts +14 -0
- package/esm/core/codegen/utils.js +86 -0
- package/esm/core/introspect/infer-tables.d.ts +5 -0
- package/esm/core/introspect/infer-tables.js +11 -4
- package/esm/core/pipeline/index.js +2 -1
- package/esm/types/config.d.ts +6 -0
- package/esm/types/config.js +1 -0
- package/esm/types/schema.d.ts +4 -0
- package/package.json +6 -6
- package/types/config.d.ts +6 -0
- package/types/config.js +1 -0
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|