@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
|
@@ -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
|
+
}
|
package/generators/select.d.ts
CHANGED
|
@@ -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
|
*/
|
package/generators/select.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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,
|
|
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[
|
|
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:
|
|
153
|
+
type: createInputType,
|
|
164
154
|
isNotNull: true,
|
|
165
155
|
isArray: false,
|
|
166
156
|
isArrayNotNull: false,
|
|
167
157
|
properties: {
|
|
168
|
-
[
|
|
169
|
-
name:
|
|
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[
|
|
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:
|
|
179
|
+
type: (0, naming_helpers_1.toUpdateInputTypeName)(modelName),
|
|
190
180
|
isNotNull: true,
|
|
191
181
|
isArray: false,
|
|
192
182
|
isArrayNotNull: false,
|
|
193
183
|
properties: {
|
|
194
|
-
|
|
195
|
-
name:
|
|
196
|
-
type:
|
|
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[
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
t.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
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.
|
|
516
|
-
|
|
517
|
-
|
|
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,
|
|
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:
|
|
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.
|
|
3
|
+
"version": "4.6.1",
|
|
4
4
|
"description": "GraphQL SDK generator for Constructive databases with React Query hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
"@babel/types": "^7.28.6",
|
|
59
59
|
"@constructive-io/graphql-types": "^3.1.1",
|
|
60
60
|
"@inquirerer/utils": "^3.2.3",
|
|
61
|
-
"@pgpmjs/core": "^6.3.
|
|
61
|
+
"@pgpmjs/core": "^6.3.2",
|
|
62
62
|
"ajv": "^8.17.1",
|
|
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.
|
|
66
|
+
"graphile-schema": "^1.2.5",
|
|
67
67
|
"graphql": "^16.9.0",
|
|
68
68
|
"inflekt": "^0.3.1",
|
|
69
69
|
"inquirerer": "^4.4.0",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"oxfmt": "^0.26.0",
|
|
73
73
|
"pg-cache": "^3.1.0",
|
|
74
74
|
"pg-env": "^1.5.0",
|
|
75
|
-
"pgsql-client": "^3.2.
|
|
76
|
-
"pgsql-seed": "^2.2.
|
|
75
|
+
"pgsql-client": "^3.2.3",
|
|
76
|
+
"pgsql-seed": "^2.2.3",
|
|
77
77
|
"undici": "^7.19.0"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"tsx": "^4.21.0",
|
|
101
101
|
"typescript": "^5.9.3"
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "c9364ffbc4d4a6b1b3750f0a5e347a7290d8d4ce"
|
|
104
104
|
}
|
package/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/types/config.js
CHANGED
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
|
@@ -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
|