@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.
Files changed (57) hide show
  1. package/core/codegen/hooks-docs-generator.js +16 -16
  2. package/core/codegen/mutations.js +6 -6
  3. package/core/codegen/orm/custom-ops-generator.d.ts +2 -2
  4. package/core/codegen/orm/custom-ops-generator.js +15 -6
  5. package/core/codegen/orm/docs-generator.js +6 -6
  6. package/core/codegen/orm/index.js +4 -3
  7. package/core/codegen/orm/input-types-generator.d.ts +1 -1
  8. package/core/codegen/orm/input-types-generator.js +41 -17
  9. package/core/codegen/queries.js +12 -10
  10. package/core/codegen/schema-types-generator.d.ts +2 -0
  11. package/core/codegen/schema-types-generator.js +41 -12
  12. package/core/codegen/shared/index.js +2 -0
  13. package/core/codegen/utils.d.ts +14 -0
  14. package/core/codegen/utils.js +87 -0
  15. package/core/introspect/infer-tables.d.ts +5 -0
  16. package/core/introspect/infer-tables.js +54 -4
  17. package/core/pipeline/index.js +2 -1
  18. package/esm/core/codegen/hooks-docs-generator.js +16 -16
  19. package/esm/core/codegen/mutations.js +6 -6
  20. package/esm/core/codegen/orm/custom-ops-generator.d.ts +2 -2
  21. package/esm/core/codegen/orm/custom-ops-generator.js +17 -8
  22. package/esm/core/codegen/orm/docs-generator.js +6 -6
  23. package/esm/core/codegen/orm/index.js +4 -3
  24. package/esm/core/codegen/orm/input-types-generator.d.ts +1 -1
  25. package/esm/core/codegen/orm/input-types-generator.js +43 -19
  26. package/esm/core/codegen/queries.js +12 -10
  27. package/esm/core/codegen/schema-types-generator.d.ts +2 -0
  28. package/esm/core/codegen/schema-types-generator.js +43 -14
  29. package/esm/core/codegen/shared/index.js +2 -0
  30. package/esm/core/codegen/utils.d.ts +14 -0
  31. package/esm/core/codegen/utils.js +86 -0
  32. package/esm/core/introspect/infer-tables.d.ts +5 -0
  33. package/esm/core/introspect/infer-tables.js +55 -5
  34. package/esm/core/pipeline/index.js +2 -1
  35. package/esm/generators/field-selector.js +32 -7
  36. package/esm/generators/mutations.d.ts +1 -2
  37. package/esm/generators/mutations.js +12 -9
  38. package/esm/generators/naming-helpers.d.ts +48 -0
  39. package/esm/generators/naming-helpers.js +154 -0
  40. package/esm/generators/select.d.ts +1 -12
  41. package/esm/generators/select.js +96 -71
  42. package/esm/types/config.d.ts +6 -0
  43. package/esm/types/config.js +1 -0
  44. package/esm/types/query.d.ts +9 -0
  45. package/esm/types/schema.d.ts +8 -0
  46. package/generators/field-selector.js +32 -7
  47. package/generators/mutations.d.ts +1 -2
  48. package/generators/mutations.js +12 -9
  49. package/generators/naming-helpers.d.ts +48 -0
  50. package/generators/naming-helpers.js +169 -0
  51. package/generators/select.d.ts +1 -12
  52. package/generators/select.js +98 -72
  53. package/package.json +6 -6
  54. package/types/config.d.ts +6 -0
  55. package/types/config.js +1 -0
  56. package/types/query.d.ts +9 -0
  57. package/types/schema.d.ts +8 -0
@@ -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
- * Convert PascalCase table name to camelCase plural for GraphQL queries
14
- * Uses the inflection library for proper pluralization
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 = camelize(modelName, true);
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[`create${modelName}`] = {
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: `Create${modelName}Input`,
109
+ type: createInputType,
121
110
  isNotNull: true,
122
111
  isArray: false,
123
112
  isArrayNotNull: false,
124
113
  properties: {
125
- [camelize(modelName, true)]: {
126
- name: camelize(modelName, true),
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[`update${modelName}`] = {
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: `Update${modelName}Input`,
135
+ type: toUpdateInputTypeName(modelName),
147
136
  isNotNull: true,
148
137
  isArray: false,
149
138
  isArrayNotNull: false,
150
139
  properties: {
151
- patch: {
152
- name: 'patch',
153
- type: `${modelName}Patch`,
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[`delete${modelName}`] = {
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: `Delete${modelName}Input`,
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: `${table.name}Filter` }),
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(t.field({ name: fieldName }));
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(t.field({
449
- name: fieldName,
450
- args: [
451
- t.argument({
452
- name: 'first',
453
- value: t.intValue({
454
- value: DEFAULT_NESTED_RELATION_FIRST.toString(),
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
- selectionSet: t.selectionSet({
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.field({
473
- name: fieldName,
474
- selectionSet: t.selectionSet({
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 = camelize(table.name, true);
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: `${table.name}Filter` }),
638
+ type: t.namedType({ type: toFilterTypeName(table.name, table) }),
614
639
  }),
615
640
  ],
616
641
  selectionSet: t.selectionSet({
@@ -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
@@ -60,6 +60,7 @@ export const DEFAULT_CONFIG = {
60
60
  },
61
61
  codegen: {
62
62
  skipQueryField: true,
63
+ comments: true,
63
64
  },
64
65
  orm: false,
65
66
  reactQuery: false,
@@ -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
  }
@@ -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
- const { belongsTo, hasOne, hasMany, manyToMany } = table.relations;
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 (!scalarFields.includes(fieldName))
264
+ if (!scalarFieldSet.has(fieldName))
241
265
  return;
242
- if (included.includes(fieldName))
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
- patch: Record<string, unknown>;
21
- };
20
+ } & Record<string, unknown>;
22
21
  }>;
23
22
  /**
24
23
  * Build PostGraphile-style DELETE mutation
@@ -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 = `create${table.name}`;
73
- const singularName = (0, inflekt_1.camelize)(table.name, true);
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: `Create${table.name}Input` }),
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 = `update${table.name}`;
132
- const singularName = (0, inflekt_1.camelize)(table.name, true);
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: `Update${table.name}Input` }),
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 = `delete${table.name}`;
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: `Delete${table.name}Input` }),
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;