@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
package/esm/generators/select.js
CHANGED
|
@@ -3,33 +3,14 @@
|
|
|
3
3
|
* Uses AST-based approach for all query generation
|
|
4
4
|
*/
|
|
5
5
|
import * as t from 'gql-ast';
|
|
6
|
-
import { OperationTypeNode, print } from 'graphql';
|
|
7
|
-
import { camelize, pluralize } from 'inflekt';
|
|
6
|
+
import { Kind, OperationTypeNode, print } from 'graphql';
|
|
8
7
|
import { TypedDocumentString } from '../client/typed-document';
|
|
9
8
|
import { getCustomAstForCleanField, requiresSubfieldSelection, } from '../core/custom-ast';
|
|
10
9
|
import { QueryBuilder } from '../core/query-builder';
|
|
11
10
|
import { convertToSelectionOptions, isRelationalField } from './field-selector';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* Example: "ActionGoal" -> "actionGoals", "User" -> "users", "Person" -> "people"
|
|
16
|
-
*/
|
|
17
|
-
export function toCamelCasePlural(tableName) {
|
|
18
|
-
// First convert to camelCase (lowercase first letter)
|
|
19
|
-
const camelCase = camelize(tableName, true);
|
|
20
|
-
// Then pluralize properly
|
|
21
|
-
return pluralize(camelCase);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Generate the PostGraphile OrderBy enum type name for a table
|
|
25
|
-
* PostGraphile uses pluralized PascalCase: "Product" -> "ProductsOrderBy"
|
|
26
|
-
* Example: "Product" -> "ProductsOrderBy", "Person" -> "PeopleOrderBy"
|
|
27
|
-
*/
|
|
28
|
-
export function toOrderByTypeName(tableName) {
|
|
29
|
-
const plural = toCamelCasePlural(tableName); // "products", "people"
|
|
30
|
-
// Capitalize first letter for PascalCase
|
|
31
|
-
return `${plural.charAt(0).toUpperCase() + plural.slice(1)}OrderBy`;
|
|
32
|
-
}
|
|
11
|
+
import { normalizeInflectionValue, toCamelCasePlural, toCamelCaseSingular, toCreateInputTypeName, toCreateMutationName, toDeleteInputTypeName, toDeleteMutationName, toFilterTypeName, toOrderByTypeName, toPatchFieldName, toUpdateInputTypeName, toUpdateMutationName, } from './naming-helpers';
|
|
12
|
+
// Re-export naming helpers for backwards compatibility
|
|
13
|
+
export { toCamelCasePlural, toOrderByTypeName } from './naming-helpers';
|
|
33
14
|
/**
|
|
34
15
|
* Convert CleanTable to MetaObject format for QueryBuilder
|
|
35
16
|
*/
|
|
@@ -90,7 +71,7 @@ export function generateIntrospectionSchema(tables) {
|
|
|
90
71
|
const schema = {};
|
|
91
72
|
for (const table of tables) {
|
|
92
73
|
const modelName = table.name;
|
|
93
|
-
const pluralName = toCamelCasePlural(modelName);
|
|
74
|
+
const pluralName = toCamelCasePlural(modelName, table);
|
|
94
75
|
// Basic field selection for the model
|
|
95
76
|
const selection = table.fields.map((field) => field.name);
|
|
96
77
|
// Add getMany query
|
|
@@ -101,15 +82,23 @@ export function generateIntrospectionSchema(tables) {
|
|
|
101
82
|
properties: convertFieldsToProperties(table.fields),
|
|
102
83
|
};
|
|
103
84
|
// Add getOne query (by ID)
|
|
104
|
-
const singularName =
|
|
85
|
+
const singularName = toCamelCaseSingular(modelName, table);
|
|
105
86
|
schema[singularName] = {
|
|
106
87
|
qtype: 'getOne',
|
|
107
88
|
model: modelName,
|
|
108
89
|
selection,
|
|
109
90
|
properties: convertFieldsToProperties(table.fields),
|
|
110
91
|
};
|
|
92
|
+
// Derive entity-specific names from introspection data
|
|
93
|
+
const patchFieldName = toPatchFieldName(modelName, table);
|
|
94
|
+
const createMutationName = toCreateMutationName(modelName, table);
|
|
95
|
+
const updateMutationName = toUpdateMutationName(modelName, table);
|
|
96
|
+
const deleteMutationName = toDeleteMutationName(modelName, table);
|
|
97
|
+
const createInputType = toCreateInputTypeName(modelName, table);
|
|
98
|
+
const patchType = normalizeInflectionValue(table.inflection?.patchType) ??
|
|
99
|
+
`${modelName}Patch`;
|
|
111
100
|
// Add create mutation
|
|
112
|
-
schema[
|
|
101
|
+
schema[createMutationName] = {
|
|
113
102
|
qtype: 'mutation',
|
|
114
103
|
mutationType: 'create',
|
|
115
104
|
model: modelName,
|
|
@@ -117,13 +106,13 @@ export function generateIntrospectionSchema(tables) {
|
|
|
117
106
|
properties: {
|
|
118
107
|
input: {
|
|
119
108
|
name: 'input',
|
|
120
|
-
type:
|
|
109
|
+
type: createInputType,
|
|
121
110
|
isNotNull: true,
|
|
122
111
|
isArray: false,
|
|
123
112
|
isArrayNotNull: false,
|
|
124
113
|
properties: {
|
|
125
|
-
[
|
|
126
|
-
name:
|
|
114
|
+
[singularName]: {
|
|
115
|
+
name: singularName,
|
|
127
116
|
type: `${modelName}Input`,
|
|
128
117
|
isNotNull: true,
|
|
129
118
|
isArray: false,
|
|
@@ -135,7 +124,7 @@ export function generateIntrospectionSchema(tables) {
|
|
|
135
124
|
},
|
|
136
125
|
};
|
|
137
126
|
// Add update mutation
|
|
138
|
-
schema[
|
|
127
|
+
schema[updateMutationName] = {
|
|
139
128
|
qtype: 'mutation',
|
|
140
129
|
mutationType: 'patch',
|
|
141
130
|
model: modelName,
|
|
@@ -143,14 +132,14 @@ export function generateIntrospectionSchema(tables) {
|
|
|
143
132
|
properties: {
|
|
144
133
|
input: {
|
|
145
134
|
name: 'input',
|
|
146
|
-
type:
|
|
135
|
+
type: toUpdateInputTypeName(modelName),
|
|
147
136
|
isNotNull: true,
|
|
148
137
|
isArray: false,
|
|
149
138
|
isArrayNotNull: false,
|
|
150
139
|
properties: {
|
|
151
|
-
|
|
152
|
-
name:
|
|
153
|
-
type:
|
|
140
|
+
[patchFieldName]: {
|
|
141
|
+
name: patchFieldName,
|
|
142
|
+
type: patchType,
|
|
154
143
|
isNotNull: true,
|
|
155
144
|
isArray: false,
|
|
156
145
|
isArrayNotNull: false,
|
|
@@ -161,7 +150,7 @@ export function generateIntrospectionSchema(tables) {
|
|
|
161
150
|
},
|
|
162
151
|
};
|
|
163
152
|
// Add delete mutation
|
|
164
|
-
schema[
|
|
153
|
+
schema[deleteMutationName] = {
|
|
165
154
|
qtype: 'mutation',
|
|
166
155
|
mutationType: 'delete',
|
|
167
156
|
model: modelName,
|
|
@@ -169,7 +158,7 @@ export function generateIntrospectionSchema(tables) {
|
|
|
169
158
|
properties: {
|
|
170
159
|
input: {
|
|
171
160
|
name: 'input',
|
|
172
|
-
type:
|
|
161
|
+
type: toDeleteInputTypeName(modelName),
|
|
173
162
|
isNotNull: true,
|
|
174
163
|
isArray: false,
|
|
175
164
|
isArrayNotNull: false,
|
|
@@ -239,7 +228,7 @@ export function buildSelect(table, allTables, options = {}) {
|
|
|
239
228
|
const tableList = Array.from(allTables);
|
|
240
229
|
const selection = convertFieldSelectionToSelectionOptions(table, tableList, options.fieldSelection);
|
|
241
230
|
// Generate query directly using AST
|
|
242
|
-
const queryString = generateSelectQueryAST(table, tableList, selection, options);
|
|
231
|
+
const queryString = generateSelectQueryAST(table, tableList, selection, options, options.relationFieldMap);
|
|
243
232
|
return new TypedDocumentString(queryString, {});
|
|
244
233
|
}
|
|
245
234
|
/**
|
|
@@ -262,10 +251,10 @@ function convertFieldSelectionToSelectionOptions(table, allTables, options) {
|
|
|
262
251
|
/**
|
|
263
252
|
* Generate SELECT query AST directly from CleanTable
|
|
264
253
|
*/
|
|
265
|
-
function generateSelectQueryAST(table, allTables, selection, options) {
|
|
266
|
-
const pluralName = toCamelCasePlural(table.name);
|
|
254
|
+
function generateSelectQueryAST(table, allTables, selection, options, relationFieldMap) {
|
|
255
|
+
const pluralName = toCamelCasePlural(table.name, table);
|
|
267
256
|
// Generate field selections
|
|
268
|
-
const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection);
|
|
257
|
+
const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap);
|
|
269
258
|
// Build the query AST
|
|
270
259
|
const variableDefinitions = [];
|
|
271
260
|
const queryArgs = [];
|
|
@@ -316,7 +305,7 @@ function generateSelectQueryAST(table, allTables, selection, options) {
|
|
|
316
305
|
if (options.where) {
|
|
317
306
|
variableDefinitions.push(t.variableDefinition({
|
|
318
307
|
variable: t.variable({ name: 'filter' }),
|
|
319
|
-
type: t.namedType({ type:
|
|
308
|
+
type: t.namedType({ type: toFilterTypeName(table.name, table) }),
|
|
320
309
|
}));
|
|
321
310
|
queryArgs.push(t.argument({
|
|
322
311
|
name: 'filter',
|
|
@@ -330,7 +319,7 @@ function generateSelectQueryAST(table, allTables, selection, options) {
|
|
|
330
319
|
// PostGraphile expects [ProductsOrderBy!] - list of non-null enum values
|
|
331
320
|
type: t.listType({
|
|
332
321
|
type: t.nonNullType({
|
|
333
|
-
type: t.namedType({ type: toOrderByTypeName(table.name) }),
|
|
322
|
+
type: t.namedType({ type: toOrderByTypeName(table.name, table) }),
|
|
334
323
|
}),
|
|
335
324
|
}),
|
|
336
325
|
}));
|
|
@@ -390,7 +379,7 @@ function generateSelectQueryAST(table, allTables, selection, options) {
|
|
|
390
379
|
/**
|
|
391
380
|
* Generate field selections from SelectionOptions
|
|
392
381
|
*/
|
|
393
|
-
function generateFieldSelectionsFromOptions(table, allTables, selection) {
|
|
382
|
+
function generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap) {
|
|
394
383
|
const DEFAULT_NESTED_RELATION_FIRST = 20;
|
|
395
384
|
if (!selection) {
|
|
396
385
|
// Default to all non-relational fields (includes complex fields like JSON, geometry, etc.)
|
|
@@ -409,6 +398,10 @@ function generateFieldSelectionsFromOptions(table, allTables, selection) {
|
|
|
409
398
|
}
|
|
410
399
|
const fieldSelections = [];
|
|
411
400
|
Object.entries(selection).forEach(([fieldName, fieldOptions]) => {
|
|
401
|
+
const resolvedField = resolveSelectionFieldName(fieldName, relationFieldMap);
|
|
402
|
+
if (!resolvedField) {
|
|
403
|
+
return; // Field mapped to null — omit it
|
|
404
|
+
}
|
|
412
405
|
if (fieldOptions === true) {
|
|
413
406
|
// Check if this field requires subfield selection
|
|
414
407
|
const field = table.fields.find((f) => f.name === fieldName);
|
|
@@ -418,7 +411,7 @@ function generateFieldSelectionsFromOptions(table, allTables, selection) {
|
|
|
418
411
|
}
|
|
419
412
|
else {
|
|
420
413
|
// Simple field selection for scalar fields
|
|
421
|
-
fieldSelections.push(
|
|
414
|
+
fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias));
|
|
422
415
|
}
|
|
423
416
|
}
|
|
424
417
|
else if (typeof fieldOptions === 'object' && fieldOptions.select) {
|
|
@@ -445,41 +438,73 @@ function generateFieldSelectionsFromOptions(table, allTables, selection) {
|
|
|
445
438
|
if (relationInfo &&
|
|
446
439
|
(relationInfo.type === 'hasMany' || relationInfo.type === 'manyToMany')) {
|
|
447
440
|
// For hasMany/manyToMany relations, wrap selections in nodes { ... }
|
|
448
|
-
fieldSelections.push(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
t.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
441
|
+
fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias, [
|
|
442
|
+
t.argument({
|
|
443
|
+
name: 'first',
|
|
444
|
+
value: t.intValue({
|
|
445
|
+
value: DEFAULT_NESTED_RELATION_FIRST.toString(),
|
|
446
|
+
}),
|
|
447
|
+
}),
|
|
448
|
+
], t.selectionSet({
|
|
449
|
+
selections: [
|
|
450
|
+
t.field({
|
|
451
|
+
name: 'nodes',
|
|
452
|
+
selectionSet: t.selectionSet({
|
|
453
|
+
selections: nestedSelections,
|
|
455
454
|
}),
|
|
456
455
|
}),
|
|
457
456
|
],
|
|
458
|
-
|
|
459
|
-
selections: [
|
|
460
|
-
t.field({
|
|
461
|
-
name: 'nodes',
|
|
462
|
-
selectionSet: t.selectionSet({
|
|
463
|
-
selections: nestedSelections,
|
|
464
|
-
}),
|
|
465
|
-
}),
|
|
466
|
-
],
|
|
467
|
-
}),
|
|
468
|
-
}));
|
|
457
|
+
})));
|
|
469
458
|
}
|
|
470
459
|
else {
|
|
471
460
|
// For belongsTo/hasOne relations, use direct selection
|
|
472
|
-
fieldSelections.push(t.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
selections: nestedSelections,
|
|
476
|
-
}),
|
|
477
|
-
}));
|
|
461
|
+
fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias, undefined, t.selectionSet({
|
|
462
|
+
selections: nestedSelections,
|
|
463
|
+
})));
|
|
478
464
|
}
|
|
479
465
|
}
|
|
480
466
|
});
|
|
481
467
|
return fieldSelections;
|
|
482
468
|
}
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
// Field aliasing helpers (back-ported from Dashboard query-generator.ts)
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
/**
|
|
473
|
+
* Resolve a field name through the optional relationFieldMap.
|
|
474
|
+
* Returns `null` if the field should be omitted (mapped to null).
|
|
475
|
+
* Returns `{ name, alias? }` where alias is set when the mapped name differs.
|
|
476
|
+
*/
|
|
477
|
+
function resolveSelectionFieldName(fieldName, relationFieldMap) {
|
|
478
|
+
if (!relationFieldMap || !(fieldName in relationFieldMap)) {
|
|
479
|
+
return { name: fieldName };
|
|
480
|
+
}
|
|
481
|
+
const mappedFieldName = relationFieldMap[fieldName];
|
|
482
|
+
if (!mappedFieldName) {
|
|
483
|
+
return null; // mapped to null → omit
|
|
484
|
+
}
|
|
485
|
+
if (mappedFieldName === fieldName) {
|
|
486
|
+
return { name: fieldName };
|
|
487
|
+
}
|
|
488
|
+
return { name: mappedFieldName, alias: fieldName };
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Create a field AST node with optional alias support.
|
|
492
|
+
* When alias is provided and differs from name, a GraphQL alias is emitted:
|
|
493
|
+
* `alias: name { … }` instead of `name { … }`
|
|
494
|
+
*/
|
|
495
|
+
function createFieldSelectionNode(name, alias, args, selectionSet) {
|
|
496
|
+
const node = t.field({ name, args, selectionSet });
|
|
497
|
+
if (!alias || alias === name) {
|
|
498
|
+
return node;
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
...node,
|
|
502
|
+
alias: {
|
|
503
|
+
kind: Kind.NAME,
|
|
504
|
+
value: alias,
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
}
|
|
483
508
|
/**
|
|
484
509
|
* Get relation information for a field
|
|
485
510
|
*/
|
|
@@ -549,7 +574,7 @@ function findRelatedTable(relationField, table, allTables) {
|
|
|
549
574
|
* Generate FindOne query AST directly from CleanTable
|
|
550
575
|
*/
|
|
551
576
|
function generateFindOneQueryAST(table) {
|
|
552
|
-
const singularName =
|
|
577
|
+
const singularName = toCamelCaseSingular(table.name, table);
|
|
553
578
|
// Generate field selections (include all non-relational fields, including complex types)
|
|
554
579
|
const fieldSelections = table.fields
|
|
555
580
|
.filter((field) => !isRelationalField(field.name, table))
|
|
@@ -601,7 +626,7 @@ function generateFindOneQueryAST(table) {
|
|
|
601
626
|
* Generate Count query AST directly from CleanTable
|
|
602
627
|
*/
|
|
603
628
|
function generateCountQueryAST(table) {
|
|
604
|
-
const pluralName = toCamelCasePlural(table.name);
|
|
629
|
+
const pluralName = toCamelCasePlural(table.name, table);
|
|
605
630
|
const ast = t.document({
|
|
606
631
|
definitions: [
|
|
607
632
|
t.operationDefinition({
|
|
@@ -610,7 +635,7 @@ function generateCountQueryAST(table) {
|
|
|
610
635
|
variableDefinitions: [
|
|
611
636
|
t.variableDefinition({
|
|
612
637
|
variable: t.variable({ name: 'filter' }),
|
|
613
|
-
type: t.namedType({ type:
|
|
638
|
+
type: t.namedType({ type: toFilterTypeName(table.name, table) }),
|
|
614
639
|
}),
|
|
615
640
|
],
|
|
616
641
|
selectionSet: t.selectionSet({
|
package/esm/types/config.d.ts
CHANGED
|
@@ -279,6 +279,12 @@ export interface GraphQLSDKConfigTarget {
|
|
|
279
279
|
codegen?: {
|
|
280
280
|
/** Skip 'query' field on mutation payloads (default: true) */
|
|
281
281
|
skipQueryField?: boolean;
|
|
282
|
+
/**
|
|
283
|
+
* Include PostgreSQL COMMENT descriptions as JSDoc comments in generated code.
|
|
284
|
+
* PostGraphile smart comments and boilerplate descriptions are automatically stripped.
|
|
285
|
+
* @default true
|
|
286
|
+
*/
|
|
287
|
+
comments?: boolean;
|
|
282
288
|
};
|
|
283
289
|
/**
|
|
284
290
|
* Whether to generate ORM client
|
package/esm/types/config.js
CHANGED
package/esm/types/query.d.ts
CHANGED
|
@@ -39,6 +39,15 @@ export interface QueryOptions {
|
|
|
39
39
|
orderBy?: OrderByItem[];
|
|
40
40
|
/** Field selection options */
|
|
41
41
|
fieldSelection?: FieldSelection;
|
|
42
|
+
/**
|
|
43
|
+
* Maps requested relation field names to actual schema field names (or null to omit).
|
|
44
|
+
* When the mapped name differs from the key, a GraphQL alias is emitted so the
|
|
45
|
+
* consumer sees a stable field name regardless of the server-side name.
|
|
46
|
+
*
|
|
47
|
+
* Example: `{ contact: 'contactByOwnerId' }` emits `contact: contactByOwnerId { … }`
|
|
48
|
+
* Pass `null` to suppress a relation entirely: `{ internalNotes: null }`
|
|
49
|
+
*/
|
|
50
|
+
relationFieldMap?: Record<string, string | null>;
|
|
42
51
|
/** Include pageInfo in response */
|
|
43
52
|
includePageInfo?: boolean;
|
|
44
53
|
}
|
package/esm/types/schema.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export interface CleanTable {
|
|
9
9
|
name: string;
|
|
10
|
+
/** Description from PostgreSQL COMMENT (smart comments stripped) */
|
|
11
|
+
description?: string;
|
|
10
12
|
fields: CleanField[];
|
|
11
13
|
relations: CleanRelations;
|
|
12
14
|
/** PostGraphile inflection rules for this table */
|
|
@@ -103,7 +105,13 @@ export interface ForeignKeyConstraint extends ConstraintInfo {
|
|
|
103
105
|
*/
|
|
104
106
|
export interface CleanField {
|
|
105
107
|
name: string;
|
|
108
|
+
/** Description from PostgreSQL COMMENT (smart comments stripped) */
|
|
109
|
+
description?: string;
|
|
106
110
|
type: CleanFieldType;
|
|
111
|
+
/** Whether the column has a NOT NULL constraint (inferred from NON_NULL wrapper on entity type field) */
|
|
112
|
+
isNotNull?: boolean | null;
|
|
113
|
+
/** Whether the column has a DEFAULT value (inferred by comparing entity vs CreateInput field nullability) */
|
|
114
|
+
hasDefault?: boolean | null;
|
|
107
115
|
}
|
|
108
116
|
/**
|
|
109
117
|
* Field type information from PostGraphile introspection
|
|
@@ -4,6 +4,31 @@ exports.convertToSelectionOptions = convertToSelectionOptions;
|
|
|
4
4
|
exports.isRelationalField = isRelationalField;
|
|
5
5
|
exports.getAvailableRelations = getAvailableRelations;
|
|
6
6
|
exports.validateFieldSelection = validateFieldSelection;
|
|
7
|
+
const relationalFieldSetCache = new WeakMap();
|
|
8
|
+
function getRelationalFieldSet(table) {
|
|
9
|
+
const cached = relationalFieldSetCache.get(table);
|
|
10
|
+
if (cached)
|
|
11
|
+
return cached;
|
|
12
|
+
const set = new Set();
|
|
13
|
+
for (const rel of table.relations.belongsTo) {
|
|
14
|
+
if (rel.fieldName)
|
|
15
|
+
set.add(rel.fieldName);
|
|
16
|
+
}
|
|
17
|
+
for (const rel of table.relations.hasOne) {
|
|
18
|
+
if (rel.fieldName)
|
|
19
|
+
set.add(rel.fieldName);
|
|
20
|
+
}
|
|
21
|
+
for (const rel of table.relations.hasMany) {
|
|
22
|
+
if (rel.fieldName)
|
|
23
|
+
set.add(rel.fieldName);
|
|
24
|
+
}
|
|
25
|
+
for (const rel of table.relations.manyToMany) {
|
|
26
|
+
if (rel.fieldName)
|
|
27
|
+
set.add(rel.fieldName);
|
|
28
|
+
}
|
|
29
|
+
relationalFieldSetCache.set(table, set);
|
|
30
|
+
return set;
|
|
31
|
+
}
|
|
7
32
|
/**
|
|
8
33
|
* Convert simplified field selection to QueryBuilder SelectionOptions
|
|
9
34
|
*/
|
|
@@ -160,11 +185,7 @@ function getNonRelationalFields(table) {
|
|
|
160
185
|
* Check if a field is relational using table metadata
|
|
161
186
|
*/
|
|
162
187
|
function isRelationalField(fieldName, table) {
|
|
163
|
-
|
|
164
|
-
return (belongsTo.some((rel) => rel.fieldName === fieldName) ||
|
|
165
|
-
hasOne.some((rel) => rel.fieldName === fieldName) ||
|
|
166
|
-
hasMany.some((rel) => rel.fieldName === fieldName) ||
|
|
167
|
-
manyToMany.some((rel) => rel.fieldName === fieldName));
|
|
188
|
+
return getRelationalFieldSet(table).has(fieldName);
|
|
168
189
|
}
|
|
169
190
|
/**
|
|
170
191
|
* Get scalar fields for a related table to include in relation queries
|
|
@@ -233,16 +254,20 @@ function getRelatedTableScalarFields(relationField, table, allTables) {
|
|
|
233
254
|
'createdAt',
|
|
234
255
|
'updatedAt',
|
|
235
256
|
];
|
|
257
|
+
const scalarFieldSet = new Set(scalarFields);
|
|
258
|
+
// Use Set for O(1) duplicate checking
|
|
259
|
+
const includedSet = new Set();
|
|
236
260
|
const included = [];
|
|
237
261
|
const push = (fieldName) => {
|
|
238
262
|
if (!fieldName)
|
|
239
263
|
return;
|
|
240
|
-
if (!
|
|
264
|
+
if (!scalarFieldSet.has(fieldName))
|
|
241
265
|
return;
|
|
242
|
-
if (
|
|
266
|
+
if (includedSet.has(fieldName))
|
|
243
267
|
return;
|
|
244
268
|
if (included.length >= MAX_RELATED_FIELDS)
|
|
245
269
|
return;
|
|
270
|
+
includedSet.add(fieldName);
|
|
246
271
|
included.push(fieldName);
|
|
247
272
|
};
|
|
248
273
|
// Always try to include stable identifiers first.
|
|
@@ -17,8 +17,7 @@ export declare function buildPostGraphileCreate(table: CleanTable, _allTables: C
|
|
|
17
17
|
export declare function buildPostGraphileUpdate(table: CleanTable, _allTables: CleanTable[], _options?: MutationOptions): TypedDocumentString<Record<string, unknown>, {
|
|
18
18
|
input: {
|
|
19
19
|
id: string | number;
|
|
20
|
-
|
|
21
|
-
};
|
|
20
|
+
} & Record<string, unknown>;
|
|
22
21
|
}>;
|
|
23
22
|
/**
|
|
24
23
|
* Build PostGraphile-style DELETE mutation
|
package/generators/mutations.js
CHANGED
|
@@ -42,10 +42,10 @@ exports.buildPostGraphileDelete = buildPostGraphileDelete;
|
|
|
42
42
|
*/
|
|
43
43
|
const t = __importStar(require("gql-ast"));
|
|
44
44
|
const graphql_1 = require("graphql");
|
|
45
|
-
const inflekt_1 = require("inflekt");
|
|
46
45
|
const typed_document_1 = require("../client/typed-document");
|
|
47
46
|
const custom_ast_1 = require("../core/custom-ast");
|
|
48
47
|
const field_selector_1 = require("./field-selector");
|
|
48
|
+
const naming_helpers_1 = require("./naming-helpers");
|
|
49
49
|
/**
|
|
50
50
|
* Generate field selections for PostGraphile mutations using custom AST logic
|
|
51
51
|
* This handles both scalar fields and complex types that require subfield selections
|
|
@@ -69,14 +69,15 @@ function generateFieldSelections(table) {
|
|
|
69
69
|
* PostGraphile expects: mutation { createTableName(input: { tableName: TableNameInput! }) { tableName { ... } } }
|
|
70
70
|
*/
|
|
71
71
|
function buildPostGraphileCreate(table, _allTables, _options = {}) {
|
|
72
|
-
const mutationName =
|
|
73
|
-
const singularName = (0,
|
|
72
|
+
const mutationName = (0, naming_helpers_1.toCreateMutationName)(table.name, table);
|
|
73
|
+
const singularName = (0, naming_helpers_1.toCamelCaseSingular)(table.name, table);
|
|
74
|
+
const inputTypeName = (0, naming_helpers_1.toCreateInputTypeName)(table.name, table);
|
|
74
75
|
// Create the variable definition for $input
|
|
75
76
|
const variableDefinitions = [
|
|
76
77
|
t.variableDefinition({
|
|
77
78
|
variable: t.variable({ name: 'input' }),
|
|
78
79
|
type: t.nonNullType({
|
|
79
|
-
type: t.namedType({ type:
|
|
80
|
+
type: t.namedType({ type: inputTypeName }),
|
|
80
81
|
}),
|
|
81
82
|
}),
|
|
82
83
|
];
|
|
@@ -128,14 +129,15 @@ function buildPostGraphileCreate(table, _allTables, _options = {}) {
|
|
|
128
129
|
* PostGraphile expects: mutation { updateTableName(input: { id: UUID!, patch: TableNamePatch! }) { tableName { ... } } }
|
|
129
130
|
*/
|
|
130
131
|
function buildPostGraphileUpdate(table, _allTables, _options = {}) {
|
|
131
|
-
const mutationName =
|
|
132
|
-
const singularName = (0,
|
|
132
|
+
const mutationName = (0, naming_helpers_1.toUpdateMutationName)(table.name, table);
|
|
133
|
+
const singularName = (0, naming_helpers_1.toCamelCaseSingular)(table.name, table);
|
|
134
|
+
const inputTypeName = (0, naming_helpers_1.toUpdateInputTypeName)(table.name);
|
|
133
135
|
// Create the variable definition for $input
|
|
134
136
|
const variableDefinitions = [
|
|
135
137
|
t.variableDefinition({
|
|
136
138
|
variable: t.variable({ name: 'input' }),
|
|
137
139
|
type: t.nonNullType({
|
|
138
|
-
type: t.namedType({ type:
|
|
140
|
+
type: t.namedType({ type: inputTypeName }),
|
|
139
141
|
}),
|
|
140
142
|
}),
|
|
141
143
|
];
|
|
@@ -187,13 +189,14 @@ function buildPostGraphileUpdate(table, _allTables, _options = {}) {
|
|
|
187
189
|
* PostGraphile expects: mutation { deleteTableName(input: { id: UUID! }) { clientMutationId } }
|
|
188
190
|
*/
|
|
189
191
|
function buildPostGraphileDelete(table, _allTables, _options = {}) {
|
|
190
|
-
const mutationName =
|
|
192
|
+
const mutationName = (0, naming_helpers_1.toDeleteMutationName)(table.name, table);
|
|
193
|
+
const inputTypeName = (0, naming_helpers_1.toDeleteInputTypeName)(table.name);
|
|
191
194
|
// Create the variable definition for $input
|
|
192
195
|
const variableDefinitions = [
|
|
193
196
|
t.variableDefinition({
|
|
194
197
|
variable: t.variable({ name: 'input' }),
|
|
195
198
|
type: t.nonNullType({
|
|
196
|
-
type: t.namedType({ type:
|
|
199
|
+
type: t.namedType({ type: inputTypeName }),
|
|
197
200
|
}),
|
|
198
201
|
}),
|
|
199
202
|
];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CleanTable } from '../types/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Safely normalise a server-provided inflection value.
|
|
4
|
+
* Returns `null` for null, undefined, or whitespace-only strings.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizeInflectionValue(value: string | null | undefined): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Convert PascalCase table name to camelCase plural for GraphQL queries.
|
|
9
|
+
* Prefers server-provided `table.query.all` / `table.inflection.allRows`
|
|
10
|
+
* when available, with guards against naive pluralisation drift and
|
|
11
|
+
* missing camelCase boundaries.
|
|
12
|
+
*
|
|
13
|
+
* Example: "ActionGoal" -> "actionGoals", "User" -> "users", "Person" -> "people"
|
|
14
|
+
*/
|
|
15
|
+
export declare function toCamelCasePlural(tableName: string, table?: CleanTable | null): string;
|
|
16
|
+
/**
|
|
17
|
+
* Convert PascalCase table name to camelCase singular field name.
|
|
18
|
+
* Prefers server-provided names when available.
|
|
19
|
+
*/
|
|
20
|
+
export declare function toCamelCaseSingular(tableName: string, table?: CleanTable | null): string;
|
|
21
|
+
export declare function toCreateMutationName(tableName: string, table?: CleanTable | null): string;
|
|
22
|
+
export declare function toUpdateMutationName(tableName: string, table?: CleanTable | null): string;
|
|
23
|
+
export declare function toDeleteMutationName(tableName: string, table?: CleanTable | null): string;
|
|
24
|
+
export declare function toCreateInputTypeName(tableName: string, table?: CleanTable | null): string;
|
|
25
|
+
export declare function toUpdateInputTypeName(tableName: string): string;
|
|
26
|
+
export declare function toDeleteInputTypeName(tableName: string): string;
|
|
27
|
+
export declare function toFilterTypeName(tableName: string, table?: CleanTable | null): string;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve PostGraphile patch field name.
|
|
30
|
+
* In v5 this is typically entity-specific: e.g. "userPatch", "contactPatch".
|
|
31
|
+
* Prefers the value discovered from the schema (`table.query.patchFieldName`
|
|
32
|
+
* or `table.inflection.patchField`), falls back to `${singularName}Patch`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function toPatchFieldName(tableName: string, table?: CleanTable | null): string;
|
|
35
|
+
/**
|
|
36
|
+
* Convert camelCase field name to SCREAMING_SNAKE_CASE for PostGraphile
|
|
37
|
+
* orderBy enums.
|
|
38
|
+
*
|
|
39
|
+
* "displayName" -> "DISPLAY_NAME_ASC"
|
|
40
|
+
* "createdAt" -> "CREATED_AT_DESC"
|
|
41
|
+
* "id" -> "ID_ASC"
|
|
42
|
+
*/
|
|
43
|
+
export declare function toOrderByEnumValue(fieldName: string, direction: 'asc' | 'desc'): string;
|
|
44
|
+
/**
|
|
45
|
+
* Generate the PostGraphile OrderBy enum type name for a table.
|
|
46
|
+
* Prefers server-provided `table.inflection.orderByType` when available.
|
|
47
|
+
*/
|
|
48
|
+
export declare function toOrderByTypeName(tableName: string, table?: CleanTable | null): string;
|