@constructive-io/graphql-codegen 4.5.3 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/codegen/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 +54 -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 +55 -5
- package/esm/core/pipeline/index.js +2 -1
- package/esm/generators/field-selector.js +32 -7
- package/esm/generators/mutations.d.ts +1 -2
- package/esm/generators/mutations.js +12 -9
- package/esm/generators/naming-helpers.d.ts +48 -0
- package/esm/generators/naming-helpers.js +154 -0
- package/esm/generators/select.d.ts +1 -12
- package/esm/generators/select.js +96 -71
- package/esm/types/config.d.ts +6 -0
- package/esm/types/config.js +1 -0
- package/esm/types/query.d.ts +9 -0
- package/esm/types/schema.d.ts +8 -0
- package/generators/field-selector.js +32 -7
- package/generators/mutations.d.ts +1 -2
- package/generators/mutations.js +12 -9
- package/generators/naming-helpers.d.ts +48 -0
- package/generators/naming-helpers.js +169 -0
- package/generators/select.d.ts +1 -12
- package/generators/select.js +98 -72
- package/package.json +6 -6
- package/types/config.d.ts +6 -0
- package/types/config.js +1 -0
- package/types/query.d.ts +9 -0
- package/types/schema.d.ts +8 -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/core/codegen/utils.d.ts
CHANGED
|
@@ -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
|
*/
|
package/core/codegen/utils.js
CHANGED
|
@@ -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,10 +226,14 @@ 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;
|
|
233
|
+
// Build a lookup of CreateXxxInput fields to infer hasDefault.
|
|
234
|
+
// If a field is NOT NULL on the entity but NOT required in CreateXxxInput,
|
|
235
|
+
// then it likely has a server-side default (serial, uuid_generate_v4, now(), etc.).
|
|
236
|
+
const createInputRequiredFields = buildCreateInputRequiredFieldSet(entityType.name, typeMap);
|
|
228
237
|
for (const field of entityType.fields) {
|
|
229
238
|
const baseTypeName = (0, introspection_1.getBaseTypeName)(field.type);
|
|
230
239
|
if (!baseTypeName)
|
|
@@ -238,14 +247,55 @@ function extractEntityFields(entityType, typeMap, entityToConnection) {
|
|
|
238
247
|
continue; // Skip relation fields
|
|
239
248
|
}
|
|
240
249
|
}
|
|
250
|
+
// Infer isNotNull from the NON_NULL wrapper on the entity type field
|
|
251
|
+
const fieldIsNotNull = (0, introspection_1.isNonNull)(field.type);
|
|
252
|
+
// Infer hasDefault: if a field is NOT NULL on the entity but NOT required
|
|
253
|
+
// in CreateXxxInput, it likely has a default value.
|
|
254
|
+
// Also: if it's absent from CreateInput entirely, it's likely computed/generated.
|
|
255
|
+
let fieldHasDefault = null;
|
|
256
|
+
if (createInputRequiredFields !== null) {
|
|
257
|
+
if (fieldIsNotNull && !createInputRequiredFields.has(field.name)) {
|
|
258
|
+
fieldHasDefault = true;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
fieldHasDefault = false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
241
264
|
// Include scalar, enum, and other non-relation fields
|
|
265
|
+
const fieldDescription = commentsEnabled ? (0, utils_1.stripSmartComments)(field.description) : undefined;
|
|
242
266
|
fields.push({
|
|
243
267
|
name: field.name,
|
|
268
|
+
...(fieldDescription ? { description: fieldDescription } : {}),
|
|
244
269
|
type: convertToCleanFieldType(field.type),
|
|
270
|
+
isNotNull: fieldIsNotNull,
|
|
271
|
+
hasDefault: fieldHasDefault,
|
|
245
272
|
});
|
|
246
273
|
}
|
|
247
274
|
return fields;
|
|
248
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Build a set of field names that are required (NON_NULL) in the CreateXxxInput type.
|
|
278
|
+
* Returns null if the CreateXxxInput type doesn't exist (no create mutation).
|
|
279
|
+
*/
|
|
280
|
+
function buildCreateInputRequiredFieldSet(entityName, typeMap) {
|
|
281
|
+
const createInputName = `Create${entityName}Input`;
|
|
282
|
+
const createInput = typeMap.get(createInputName);
|
|
283
|
+
if (!createInput?.inputFields)
|
|
284
|
+
return null;
|
|
285
|
+
// The CreateXxxInput typically has a single field like { user: UserInput! }
|
|
286
|
+
// We need to look inside the actual entity input type (e.g., UserInput)
|
|
287
|
+
const entityInputName = `${entityName}Input`;
|
|
288
|
+
const entityInput = typeMap.get(entityInputName);
|
|
289
|
+
if (!entityInput?.inputFields)
|
|
290
|
+
return null;
|
|
291
|
+
const requiredFields = new Set();
|
|
292
|
+
for (const inputField of entityInput.inputFields) {
|
|
293
|
+
if ((0, introspection_1.isNonNull)(inputField.type)) {
|
|
294
|
+
requiredFields.add(inputField.name);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return requiredFields;
|
|
298
|
+
}
|
|
249
299
|
/**
|
|
250
300
|
* Check if a type name is an entity type (has a corresponding Connection)
|
|
251
301
|
*/
|
package/core/pipeline/index.js
CHANGED
|
@@ -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
|
-
|
|
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;
|