@constructive-io/graphql-codegen 4.6.0 → 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.
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeInflectionValue = normalizeInflectionValue;
4
+ exports.toCamelCasePlural = toCamelCasePlural;
5
+ exports.toCamelCaseSingular = toCamelCaseSingular;
6
+ exports.toCreateMutationName = toCreateMutationName;
7
+ exports.toUpdateMutationName = toUpdateMutationName;
8
+ exports.toDeleteMutationName = toDeleteMutationName;
9
+ exports.toCreateInputTypeName = toCreateInputTypeName;
10
+ exports.toUpdateInputTypeName = toUpdateInputTypeName;
11
+ exports.toDeleteInputTypeName = toDeleteInputTypeName;
12
+ exports.toFilterTypeName = toFilterTypeName;
13
+ exports.toPatchFieldName = toPatchFieldName;
14
+ exports.toOrderByEnumValue = toOrderByEnumValue;
15
+ exports.toOrderByTypeName = toOrderByTypeName;
16
+ /**
17
+ * Server-aware naming helpers for GraphQL query/mutation generation.
18
+ *
19
+ * These functions prefer names already discovered from the GraphQL schema
20
+ * (stored on `table.query` and `table.inflection` by `infer-tables.ts`)
21
+ * and fall back to local inflection when introspection data is unavailable.
22
+ *
23
+ * Back-ported from Dashboard's `packages/data/src/query-generator.ts`.
24
+ */
25
+ const inflekt_1 = require("inflekt");
26
+ // ---------------------------------------------------------------------------
27
+ // Internal helpers
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Safely normalise a server-provided inflection value.
31
+ * Returns `null` for null, undefined, or whitespace-only strings.
32
+ */
33
+ function normalizeInflectionValue(value) {
34
+ if (typeof value !== 'string')
35
+ return null;
36
+ const trimmed = value.trim();
37
+ return trimmed.length > 0 ? trimmed : null;
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Plural / Singular
41
+ // ---------------------------------------------------------------------------
42
+ /**
43
+ * Convert PascalCase table name to camelCase plural for GraphQL queries.
44
+ * Prefers server-provided `table.query.all` / `table.inflection.allRows`
45
+ * when available, with guards against naive pluralisation drift and
46
+ * missing camelCase boundaries.
47
+ *
48
+ * Example: "ActionGoal" -> "actionGoals", "User" -> "users", "Person" -> "people"
49
+ */
50
+ function toCamelCasePlural(tableName, table) {
51
+ const singular = (0, inflekt_1.camelize)(tableName, true);
52
+ const inflectedPlural = (0, inflekt_1.pluralize)(singular);
53
+ const serverPluralCandidates = [
54
+ table?.query?.all,
55
+ table?.inflection?.allRows,
56
+ ];
57
+ for (const candidateRaw of serverPluralCandidates) {
58
+ const candidate = normalizeInflectionValue(candidateRaw);
59
+ if (!candidate)
60
+ continue;
61
+ // Guard against known fallback drift:
62
+ // 1. Naive pluralisation: "activitys" instead of "activities"
63
+ const isNaivePlural = candidate === `${singular}s` && candidate !== inflectedPlural;
64
+ // 2. Missing camelCase boundaries: "deliveryzones" instead of "deliveryZones"
65
+ const isMiscased = candidate !== inflectedPlural &&
66
+ candidate.toLowerCase() === inflectedPlural.toLowerCase();
67
+ if (isNaivePlural || isMiscased)
68
+ continue;
69
+ return candidate;
70
+ }
71
+ return inflectedPlural;
72
+ }
73
+ /**
74
+ * Convert PascalCase table name to camelCase singular field name.
75
+ * Prefers server-provided names when available.
76
+ */
77
+ function toCamelCaseSingular(tableName, table) {
78
+ const localSingular = (0, inflekt_1.camelize)(tableName, true);
79
+ for (const candidateRaw of [
80
+ table?.query?.one,
81
+ table?.inflection?.tableFieldName,
82
+ ]) {
83
+ const candidate = normalizeInflectionValue(candidateRaw);
84
+ if (!candidate)
85
+ continue;
86
+ // Reject miscased versions: "deliveryzone" vs "deliveryZone"
87
+ if (candidate !== localSingular &&
88
+ candidate.toLowerCase() === localSingular.toLowerCase())
89
+ continue;
90
+ return candidate;
91
+ }
92
+ return localSingular;
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Mutation names
96
+ // ---------------------------------------------------------------------------
97
+ function toCreateMutationName(tableName, table) {
98
+ return (normalizeInflectionValue(table?.query?.create) ?? `create${tableName}`);
99
+ }
100
+ function toUpdateMutationName(tableName, table) {
101
+ return (normalizeInflectionValue(table?.query?.update) ?? `update${tableName}`);
102
+ }
103
+ function toDeleteMutationName(tableName, table) {
104
+ return (normalizeInflectionValue(table?.query?.delete) ?? `delete${tableName}`);
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // Input / type names
108
+ // ---------------------------------------------------------------------------
109
+ function toCreateInputTypeName(tableName, table) {
110
+ return (normalizeInflectionValue(table?.inflection?.createInputType) ??
111
+ `Create${tableName}Input`);
112
+ }
113
+ function toUpdateInputTypeName(tableName) {
114
+ return `Update${tableName}Input`;
115
+ }
116
+ function toDeleteInputTypeName(tableName) {
117
+ return `Delete${tableName}Input`;
118
+ }
119
+ function toFilterTypeName(tableName, table) {
120
+ return (normalizeInflectionValue(table?.inflection?.filterType) ??
121
+ `${tableName}Filter`);
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // Patch field name
125
+ // ---------------------------------------------------------------------------
126
+ /**
127
+ * Resolve PostGraphile patch field name.
128
+ * In v5 this is typically entity-specific: e.g. "userPatch", "contactPatch".
129
+ * Prefers the value discovered from the schema (`table.query.patchFieldName`
130
+ * or `table.inflection.patchField`), falls back to `${singularName}Patch`.
131
+ */
132
+ function toPatchFieldName(tableName, table) {
133
+ // First check the patch field name discovered from UpdateXxxInput
134
+ const introspectedPatch = normalizeInflectionValue(table?.query?.patchFieldName);
135
+ if (introspectedPatch)
136
+ return introspectedPatch;
137
+ // Then check the inflection table
138
+ const explicitPatchField = normalizeInflectionValue(table?.inflection?.patchField);
139
+ if (explicitPatchField)
140
+ return explicitPatchField;
141
+ return `${toCamelCaseSingular(tableName, table)}Patch`;
142
+ }
143
+ // ---------------------------------------------------------------------------
144
+ // OrderBy helpers
145
+ // ---------------------------------------------------------------------------
146
+ /**
147
+ * Convert camelCase field name to SCREAMING_SNAKE_CASE for PostGraphile
148
+ * orderBy enums.
149
+ *
150
+ * "displayName" -> "DISPLAY_NAME_ASC"
151
+ * "createdAt" -> "CREATED_AT_DESC"
152
+ * "id" -> "ID_ASC"
153
+ */
154
+ function toOrderByEnumValue(fieldName, direction) {
155
+ const screaming = fieldName
156
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
157
+ .toUpperCase();
158
+ return `${screaming}_${direction.toUpperCase()}`;
159
+ }
160
+ /**
161
+ * Generate the PostGraphile OrderBy enum type name for a table.
162
+ * Prefers server-provided `table.inflection.orderByType` when available.
163
+ */
164
+ function toOrderByTypeName(tableName, table) {
165
+ if (table?.inflection?.orderByType)
166
+ return table.inflection.orderByType;
167
+ const plural = toCamelCasePlural(tableName, table);
168
+ return `${plural.charAt(0).toUpperCase() + plural.slice(1)}OrderBy`;
169
+ }
@@ -3,18 +3,7 @@ import { QueryBuilder } from '../core/query-builder';
3
3
  import type { IntrospectionSchema, MetaObject } from '../core/types';
4
4
  import type { QueryOptions } from '../types/query';
5
5
  import type { CleanTable } from '../types/schema';
6
- /**
7
- * Convert PascalCase table name to camelCase plural for GraphQL queries
8
- * Uses the inflection library for proper pluralization
9
- * Example: "ActionGoal" -> "actionGoals", "User" -> "users", "Person" -> "people"
10
- */
11
- export declare function toCamelCasePlural(tableName: string): string;
12
- /**
13
- * Generate the PostGraphile OrderBy enum type name for a table
14
- * PostGraphile uses pluralized PascalCase: "Product" -> "ProductsOrderBy"
15
- * Example: "Product" -> "ProductsOrderBy", "Person" -> "PeopleOrderBy"
16
- */
17
- export declare function toOrderByTypeName(tableName: string): string;
6
+ export { toCamelCasePlural, toOrderByTypeName } from './naming-helpers';
18
7
  /**
19
8
  * Convert CleanTable to MetaObject format for QueryBuilder
20
9
  */
@@ -33,8 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.toCamelCasePlural = toCamelCasePlural;
37
- exports.toOrderByTypeName = toOrderByTypeName;
36
+ exports.toOrderByTypeName = exports.toCamelCasePlural = void 0;
38
37
  exports.cleanTableToMetaObject = cleanTableToMetaObject;
39
38
  exports.generateIntrospectionSchema = generateIntrospectionSchema;
40
39
  exports.createASTQueryBuilder = createASTQueryBuilder;
@@ -47,32 +46,15 @@ exports.buildCount = buildCount;
47
46
  */
48
47
  const t = __importStar(require("gql-ast"));
49
48
  const graphql_1 = require("graphql");
50
- const inflekt_1 = require("inflekt");
51
49
  const typed_document_1 = require("../client/typed-document");
52
50
  const custom_ast_1 = require("../core/custom-ast");
53
51
  const query_builder_1 = require("../core/query-builder");
54
52
  const field_selector_1 = require("./field-selector");
55
- /**
56
- * Convert PascalCase table name to camelCase plural for GraphQL queries
57
- * Uses the inflection library for proper pluralization
58
- * Example: "ActionGoal" -> "actionGoals", "User" -> "users", "Person" -> "people"
59
- */
60
- function toCamelCasePlural(tableName) {
61
- // First convert to camelCase (lowercase first letter)
62
- const camelCase = (0, inflekt_1.camelize)(tableName, true);
63
- // Then pluralize properly
64
- return (0, inflekt_1.pluralize)(camelCase);
65
- }
66
- /**
67
- * Generate the PostGraphile OrderBy enum type name for a table
68
- * PostGraphile uses pluralized PascalCase: "Product" -> "ProductsOrderBy"
69
- * Example: "Product" -> "ProductsOrderBy", "Person" -> "PeopleOrderBy"
70
- */
71
- function toOrderByTypeName(tableName) {
72
- const plural = toCamelCasePlural(tableName); // "products", "people"
73
- // Capitalize first letter for PascalCase
74
- return `${plural.charAt(0).toUpperCase() + plural.slice(1)}OrderBy`;
75
- }
53
+ const naming_helpers_1 = require("./naming-helpers");
54
+ // Re-export naming helpers for backwards compatibility
55
+ var naming_helpers_2 = require("./naming-helpers");
56
+ Object.defineProperty(exports, "toCamelCasePlural", { enumerable: true, get: function () { return naming_helpers_2.toCamelCasePlural; } });
57
+ Object.defineProperty(exports, "toOrderByTypeName", { enumerable: true, get: function () { return naming_helpers_2.toOrderByTypeName; } });
76
58
  /**
77
59
  * Convert CleanTable to MetaObject format for QueryBuilder
78
60
  */
@@ -133,7 +115,7 @@ function generateIntrospectionSchema(tables) {
133
115
  const schema = {};
134
116
  for (const table of tables) {
135
117
  const modelName = table.name;
136
- const pluralName = toCamelCasePlural(modelName);
118
+ const pluralName = (0, naming_helpers_1.toCamelCasePlural)(modelName, table);
137
119
  // Basic field selection for the model
138
120
  const selection = table.fields.map((field) => field.name);
139
121
  // Add getMany query
@@ -144,15 +126,23 @@ function generateIntrospectionSchema(tables) {
144
126
  properties: convertFieldsToProperties(table.fields),
145
127
  };
146
128
  // Add getOne query (by ID)
147
- const singularName = (0, inflekt_1.camelize)(modelName, true);
129
+ const singularName = (0, naming_helpers_1.toCamelCaseSingular)(modelName, table);
148
130
  schema[singularName] = {
149
131
  qtype: 'getOne',
150
132
  model: modelName,
151
133
  selection,
152
134
  properties: convertFieldsToProperties(table.fields),
153
135
  };
136
+ // Derive entity-specific names from introspection data
137
+ const patchFieldName = (0, naming_helpers_1.toPatchFieldName)(modelName, table);
138
+ const createMutationName = (0, naming_helpers_1.toCreateMutationName)(modelName, table);
139
+ const updateMutationName = (0, naming_helpers_1.toUpdateMutationName)(modelName, table);
140
+ const deleteMutationName = (0, naming_helpers_1.toDeleteMutationName)(modelName, table);
141
+ const createInputType = (0, naming_helpers_1.toCreateInputTypeName)(modelName, table);
142
+ const patchType = (0, naming_helpers_1.normalizeInflectionValue)(table.inflection?.patchType) ??
143
+ `${modelName}Patch`;
154
144
  // Add create mutation
155
- schema[`create${modelName}`] = {
145
+ schema[createMutationName] = {
156
146
  qtype: 'mutation',
157
147
  mutationType: 'create',
158
148
  model: modelName,
@@ -160,13 +150,13 @@ function generateIntrospectionSchema(tables) {
160
150
  properties: {
161
151
  input: {
162
152
  name: 'input',
163
- type: `Create${modelName}Input`,
153
+ type: createInputType,
164
154
  isNotNull: true,
165
155
  isArray: false,
166
156
  isArrayNotNull: false,
167
157
  properties: {
168
- [(0, inflekt_1.camelize)(modelName, true)]: {
169
- name: (0, inflekt_1.camelize)(modelName, true),
158
+ [singularName]: {
159
+ name: singularName,
170
160
  type: `${modelName}Input`,
171
161
  isNotNull: true,
172
162
  isArray: false,
@@ -178,7 +168,7 @@ function generateIntrospectionSchema(tables) {
178
168
  },
179
169
  };
180
170
  // Add update mutation
181
- schema[`update${modelName}`] = {
171
+ schema[updateMutationName] = {
182
172
  qtype: 'mutation',
183
173
  mutationType: 'patch',
184
174
  model: modelName,
@@ -186,14 +176,14 @@ function generateIntrospectionSchema(tables) {
186
176
  properties: {
187
177
  input: {
188
178
  name: 'input',
189
- type: `Update${modelName}Input`,
179
+ type: (0, naming_helpers_1.toUpdateInputTypeName)(modelName),
190
180
  isNotNull: true,
191
181
  isArray: false,
192
182
  isArrayNotNull: false,
193
183
  properties: {
194
- patch: {
195
- name: 'patch',
196
- type: `${modelName}Patch`,
184
+ [patchFieldName]: {
185
+ name: patchFieldName,
186
+ type: patchType,
197
187
  isNotNull: true,
198
188
  isArray: false,
199
189
  isArrayNotNull: false,
@@ -204,7 +194,7 @@ function generateIntrospectionSchema(tables) {
204
194
  },
205
195
  };
206
196
  // Add delete mutation
207
- schema[`delete${modelName}`] = {
197
+ schema[deleteMutationName] = {
208
198
  qtype: 'mutation',
209
199
  mutationType: 'delete',
210
200
  model: modelName,
@@ -212,7 +202,7 @@ function generateIntrospectionSchema(tables) {
212
202
  properties: {
213
203
  input: {
214
204
  name: 'input',
215
- type: `Delete${modelName}Input`,
205
+ type: (0, naming_helpers_1.toDeleteInputTypeName)(modelName),
216
206
  isNotNull: true,
217
207
  isArray: false,
218
208
  isArrayNotNull: false,
@@ -282,7 +272,7 @@ function buildSelect(table, allTables, options = {}) {
282
272
  const tableList = Array.from(allTables);
283
273
  const selection = convertFieldSelectionToSelectionOptions(table, tableList, options.fieldSelection);
284
274
  // Generate query directly using AST
285
- const queryString = generateSelectQueryAST(table, tableList, selection, options);
275
+ const queryString = generateSelectQueryAST(table, tableList, selection, options, options.relationFieldMap);
286
276
  return new typed_document_1.TypedDocumentString(queryString, {});
287
277
  }
288
278
  /**
@@ -305,10 +295,10 @@ function convertFieldSelectionToSelectionOptions(table, allTables, options) {
305
295
  /**
306
296
  * Generate SELECT query AST directly from CleanTable
307
297
  */
308
- function generateSelectQueryAST(table, allTables, selection, options) {
309
- const pluralName = toCamelCasePlural(table.name);
298
+ function generateSelectQueryAST(table, allTables, selection, options, relationFieldMap) {
299
+ const pluralName = (0, naming_helpers_1.toCamelCasePlural)(table.name, table);
310
300
  // Generate field selections
311
- const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection);
301
+ const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap);
312
302
  // Build the query AST
313
303
  const variableDefinitions = [];
314
304
  const queryArgs = [];
@@ -359,7 +349,7 @@ function generateSelectQueryAST(table, allTables, selection, options) {
359
349
  if (options.where) {
360
350
  variableDefinitions.push(t.variableDefinition({
361
351
  variable: t.variable({ name: 'filter' }),
362
- type: t.namedType({ type: `${table.name}Filter` }),
352
+ type: t.namedType({ type: (0, naming_helpers_1.toFilterTypeName)(table.name, table) }),
363
353
  }));
364
354
  queryArgs.push(t.argument({
365
355
  name: 'filter',
@@ -373,7 +363,7 @@ function generateSelectQueryAST(table, allTables, selection, options) {
373
363
  // PostGraphile expects [ProductsOrderBy!] - list of non-null enum values
374
364
  type: t.listType({
375
365
  type: t.nonNullType({
376
- type: t.namedType({ type: toOrderByTypeName(table.name) }),
366
+ type: t.namedType({ type: (0, naming_helpers_1.toOrderByTypeName)(table.name, table) }),
377
367
  }),
378
368
  }),
379
369
  }));
@@ -433,7 +423,7 @@ function generateSelectQueryAST(table, allTables, selection, options) {
433
423
  /**
434
424
  * Generate field selections from SelectionOptions
435
425
  */
436
- function generateFieldSelectionsFromOptions(table, allTables, selection) {
426
+ function generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap) {
437
427
  const DEFAULT_NESTED_RELATION_FIRST = 20;
438
428
  if (!selection) {
439
429
  // Default to all non-relational fields (includes complex fields like JSON, geometry, etc.)
@@ -452,6 +442,10 @@ function generateFieldSelectionsFromOptions(table, allTables, selection) {
452
442
  }
453
443
  const fieldSelections = [];
454
444
  Object.entries(selection).forEach(([fieldName, fieldOptions]) => {
445
+ const resolvedField = resolveSelectionFieldName(fieldName, relationFieldMap);
446
+ if (!resolvedField) {
447
+ return; // Field mapped to null — omit it
448
+ }
455
449
  if (fieldOptions === true) {
456
450
  // Check if this field requires subfield selection
457
451
  const field = table.fields.find((f) => f.name === fieldName);
@@ -461,7 +455,7 @@ function generateFieldSelectionsFromOptions(table, allTables, selection) {
461
455
  }
462
456
  else {
463
457
  // Simple field selection for scalar fields
464
- fieldSelections.push(t.field({ name: fieldName }));
458
+ fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias));
465
459
  }
466
460
  }
467
461
  else if (typeof fieldOptions === 'object' && fieldOptions.select) {
@@ -488,41 +482,73 @@ function generateFieldSelectionsFromOptions(table, allTables, selection) {
488
482
  if (relationInfo &&
489
483
  (relationInfo.type === 'hasMany' || relationInfo.type === 'manyToMany')) {
490
484
  // For hasMany/manyToMany relations, wrap selections in nodes { ... }
491
- fieldSelections.push(t.field({
492
- name: fieldName,
493
- args: [
494
- t.argument({
495
- name: 'first',
496
- value: t.intValue({
497
- value: DEFAULT_NESTED_RELATION_FIRST.toString(),
485
+ fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias, [
486
+ t.argument({
487
+ name: 'first',
488
+ value: t.intValue({
489
+ value: DEFAULT_NESTED_RELATION_FIRST.toString(),
490
+ }),
491
+ }),
492
+ ], t.selectionSet({
493
+ selections: [
494
+ t.field({
495
+ name: 'nodes',
496
+ selectionSet: t.selectionSet({
497
+ selections: nestedSelections,
498
498
  }),
499
499
  }),
500
500
  ],
501
- selectionSet: t.selectionSet({
502
- selections: [
503
- t.field({
504
- name: 'nodes',
505
- selectionSet: t.selectionSet({
506
- selections: nestedSelections,
507
- }),
508
- }),
509
- ],
510
- }),
511
- }));
501
+ })));
512
502
  }
513
503
  else {
514
504
  // For belongsTo/hasOne relations, use direct selection
515
- fieldSelections.push(t.field({
516
- name: fieldName,
517
- selectionSet: t.selectionSet({
518
- selections: nestedSelections,
519
- }),
520
- }));
505
+ fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias, undefined, t.selectionSet({
506
+ selections: nestedSelections,
507
+ })));
521
508
  }
522
509
  }
523
510
  });
524
511
  return fieldSelections;
525
512
  }
513
+ // ---------------------------------------------------------------------------
514
+ // Field aliasing helpers (back-ported from Dashboard query-generator.ts)
515
+ // ---------------------------------------------------------------------------
516
+ /**
517
+ * Resolve a field name through the optional relationFieldMap.
518
+ * Returns `null` if the field should be omitted (mapped to null).
519
+ * Returns `{ name, alias? }` where alias is set when the mapped name differs.
520
+ */
521
+ function resolveSelectionFieldName(fieldName, relationFieldMap) {
522
+ if (!relationFieldMap || !(fieldName in relationFieldMap)) {
523
+ return { name: fieldName };
524
+ }
525
+ const mappedFieldName = relationFieldMap[fieldName];
526
+ if (!mappedFieldName) {
527
+ return null; // mapped to null → omit
528
+ }
529
+ if (mappedFieldName === fieldName) {
530
+ return { name: fieldName };
531
+ }
532
+ return { name: mappedFieldName, alias: fieldName };
533
+ }
534
+ /**
535
+ * Create a field AST node with optional alias support.
536
+ * When alias is provided and differs from name, a GraphQL alias is emitted:
537
+ * `alias: name { … }` instead of `name { … }`
538
+ */
539
+ function createFieldSelectionNode(name, alias, args, selectionSet) {
540
+ const node = t.field({ name, args, selectionSet });
541
+ if (!alias || alias === name) {
542
+ return node;
543
+ }
544
+ return {
545
+ ...node,
546
+ alias: {
547
+ kind: graphql_1.Kind.NAME,
548
+ value: alias,
549
+ },
550
+ };
551
+ }
526
552
  /**
527
553
  * Get relation information for a field
528
554
  */
@@ -592,7 +618,7 @@ function findRelatedTable(relationField, table, allTables) {
592
618
  * Generate FindOne query AST directly from CleanTable
593
619
  */
594
620
  function generateFindOneQueryAST(table) {
595
- const singularName = (0, inflekt_1.camelize)(table.name, true);
621
+ const singularName = (0, naming_helpers_1.toCamelCaseSingular)(table.name, table);
596
622
  // Generate field selections (include all non-relational fields, including complex types)
597
623
  const fieldSelections = table.fields
598
624
  .filter((field) => !(0, field_selector_1.isRelationalField)(field.name, table))
@@ -644,7 +670,7 @@ function generateFindOneQueryAST(table) {
644
670
  * Generate Count query AST directly from CleanTable
645
671
  */
646
672
  function generateCountQueryAST(table) {
647
- const pluralName = toCamelCasePlural(table.name);
673
+ const pluralName = (0, naming_helpers_1.toCamelCasePlural)(table.name, table);
648
674
  const ast = t.document({
649
675
  definitions: [
650
676
  t.operationDefinition({
@@ -653,7 +679,7 @@ function generateCountQueryAST(table) {
653
679
  variableDefinitions: [
654
680
  t.variableDefinition({
655
681
  variable: t.variable({ name: 'filter' }),
656
- type: t.namedType({ type: `${table.name}Filter` }),
682
+ type: t.namedType({ type: (0, naming_helpers_1.toFilterTypeName)(table.name, table) }),
657
683
  }),
658
684
  ],
659
685
  selectionSet: t.selectionSet({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-codegen",
3
- "version": "4.6.0",
3
+ "version": "4.6.1",
4
4
  "description": "GraphQL SDK generator for Constructive databases with React Query hooks",
5
5
  "keywords": [
6
6
  "graphql",
@@ -63,7 +63,7 @@
63
63
  "deepmerge": "^4.3.1",
64
64
  "find-and-require-package-json": "^0.9.0",
65
65
  "gql-ast": "^3.1.0",
66
- "graphile-schema": "^1.2.4",
66
+ "graphile-schema": "^1.2.5",
67
67
  "graphql": "^16.9.0",
68
68
  "inflekt": "^0.3.1",
69
69
  "inquirerer": "^4.4.0",
@@ -100,5 +100,5 @@
100
100
  "tsx": "^4.21.0",
101
101
  "typescript": "^5.9.3"
102
102
  },
103
- "gitHead": "1b17d61b375abaa04310abe05c2a3ca5881ac650"
103
+ "gitHead": "c9364ffbc4d4a6b1b3750f0a5e347a7290d8d4ce"
104
104
  }
package/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/types/schema.d.ts CHANGED
@@ -108,6 +108,10 @@ export interface CleanField {
108
108
  /** Description from PostgreSQL COMMENT (smart comments stripped) */
109
109
  description?: string;
110
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;
111
115
  }
112
116
  /**
113
117
  * Field type information from PostGraphile introspection