@graphoria/server 0.1.5 → 0.2.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 (135) hide show
  1. package/dist/src/analyzeQuery/resolveVariables.d.ts.map +1 -1
  2. package/dist/src/authentication/jwt.d.ts.map +1 -1
  3. package/dist/src/authentication/paseto.d.ts.map +1 -1
  4. package/dist/src/authentication/tokenRepository.d.ts +2 -1
  5. package/dist/src/authentication/tokenRepository.d.ts.map +1 -1
  6. package/dist/src/config/helpers/operationHelper.d.ts +60 -6
  7. package/dist/src/config/helpers/operationHelper.d.ts.map +1 -1
  8. package/dist/src/config/helpers/operationHelper.test.d.ts +2 -0
  9. package/dist/src/config/helpers/operationHelper.test.d.ts.map +1 -0
  10. package/dist/src/config/index.d.ts +65 -60
  11. package/dist/src/config/index.d.ts.map +1 -1
  12. package/dist/src/config/types/ai.d.ts +9 -3
  13. package/dist/src/config/types/ai.d.ts.map +1 -1
  14. package/dist/src/config/types/auth.d.ts +55 -15
  15. package/dist/src/config/types/auth.d.ts.map +1 -1
  16. package/dist/src/config/types/cron.d.ts +96 -2
  17. package/dist/src/config/types/cron.d.ts.map +1 -1
  18. package/dist/src/config/types/db.d.ts +102 -29
  19. package/dist/src/config/types/db.d.ts.map +1 -1
  20. package/dist/src/config/types/index.d.ts +1 -1
  21. package/dist/src/config/types/index.d.ts.map +1 -1
  22. package/dist/src/config/types/operation.d.ts +49 -17
  23. package/dist/src/config/types/operation.d.ts.map +1 -1
  24. package/dist/src/config/types/queue.d.ts +61 -14
  25. package/dist/src/config/types/queue.d.ts.map +1 -1
  26. package/dist/src/config/types/remote-rest.d.ts +10 -1
  27. package/dist/src/config/types/remote-rest.d.ts.map +1 -1
  28. package/dist/src/config/types/remote-schema.d.ts +13 -3
  29. package/dist/src/config/types/remote-schema.d.ts.map +1 -1
  30. package/dist/src/config/types/virtual-columns.d.ts +1 -1
  31. package/dist/src/config/types/virtual-columns.d.ts.map +1 -1
  32. package/dist/src/configuration/getSchemas/index.d.ts +6 -4
  33. package/dist/src/configuration/getSchemas/index.d.ts.map +1 -1
  34. package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts +9 -2
  35. package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts.map +1 -1
  36. package/dist/src/configuration/getSchemas/mergeEntities/index.test.d.ts +2 -0
  37. package/dist/src/configuration/getSchemas/mergeEntities/index.test.d.ts.map +1 -0
  38. package/dist/src/configuration/getSchemas/type-definition-generator/index.d.ts.map +1 -1
  39. package/dist/src/configuration/index.d.ts +5 -4
  40. package/dist/src/configuration/index.d.ts.map +1 -1
  41. package/dist/src/configuration/rest/generateOpenAPI.d.ts.map +1 -1
  42. package/dist/src/configuration/rest/handleRESTRequestFactory.d.ts.map +1 -1
  43. package/dist/src/configuration/rest/parseStringParams.d.ts +15 -0
  44. package/dist/src/configuration/rest/parseStringParams.d.ts.map +1 -0
  45. package/dist/src/configuration/rest/parseStringParams.test.d.ts +2 -0
  46. package/dist/src/configuration/rest/parseStringParams.test.d.ts.map +1 -0
  47. package/dist/src/databases/common.d.ts +9 -1
  48. package/dist/src/databases/common.d.ts.map +1 -1
  49. package/dist/src/databases/directives.d.ts.map +1 -1
  50. package/dist/src/databases/engines/mssql/connection.d.ts +1 -1
  51. package/dist/src/databases/engines/mssql/connection.d.ts.map +1 -1
  52. package/dist/src/databases/engines/mssql/getStructure.d.ts +1 -0
  53. package/dist/src/databases/engines/mssql/getStructure.d.ts.map +1 -1
  54. package/dist/src/databases/engines/mssql/query/index.d.ts.map +1 -1
  55. package/dist/src/databases/engines/mysql/connection.d.ts.map +1 -1
  56. package/dist/src/databases/engines/mysql/getStructure.d.ts +1 -0
  57. package/dist/src/databases/engines/mysql/getStructure.d.ts.map +1 -1
  58. package/dist/src/databases/engines/mysql/placeholders.d.ts +22 -0
  59. package/dist/src/databases/engines/mysql/placeholders.d.ts.map +1 -0
  60. package/dist/src/databases/engines/mysql/placeholders.test.d.ts +2 -0
  61. package/dist/src/databases/engines/mysql/placeholders.test.d.ts.map +1 -0
  62. package/dist/src/databases/engines/mysql/query/index.d.ts.map +1 -1
  63. package/dist/src/databases/engines/postgresql/getStructure.d.ts +1 -0
  64. package/dist/src/databases/engines/postgresql/getStructure.d.ts.map +1 -1
  65. package/dist/src/databases/engines/postgresql/query/index.d.ts.map +1 -1
  66. package/dist/src/databases/high-level-operations.d.ts +1 -0
  67. package/dist/src/databases/high-level-operations.d.ts.map +1 -1
  68. package/dist/src/databases/identifierMapping.test.d.ts +2 -0
  69. package/dist/src/databases/identifierMapping.test.d.ts.map +1 -0
  70. package/dist/src/databases/metadata/structure.d.ts +1 -0
  71. package/dist/src/databases/metadata/structure.d.ts.map +1 -1
  72. package/dist/src/databases/sqlTypeUtils.d.ts.map +1 -1
  73. package/dist/src/databases/transformers/data-transformers.d.ts +1 -0
  74. package/dist/src/databases/transformers/data-transformers.d.ts.map +1 -1
  75. package/dist/src/databases/transformers/genResolverName.d.ts.map +1 -1
  76. package/dist/src/databases/transformers/graphqlName.d.ts +23 -0
  77. package/dist/src/databases/transformers/graphqlName.d.ts.map +1 -0
  78. package/dist/src/databases/transformers/graphqlName.test.d.ts +2 -0
  79. package/dist/src/databases/transformers/graphqlName.test.d.ts.map +1 -0
  80. package/dist/src/singletons/env.d.ts +2 -2
  81. package/dist/src/types/env.d.ts +56 -56
  82. package/dist/src/types/resolver.d.ts +12 -2
  83. package/dist/src/types/resolver.d.ts.map +1 -1
  84. package/dist/src/types/zod/auth.d.ts +6 -6
  85. package/dist/src/types/zod/configuration.d.ts +52 -52
  86. package/dist/src/types/zod/configuration.d.ts.map +1 -1
  87. package/dist/src/types/zod/cron.d.ts +1 -1
  88. package/dist/src/types/zod/cron.d.ts.map +1 -1
  89. package/dist/src/types/zod/db.d.ts +21 -12
  90. package/dist/src/types/zod/db.d.ts.map +1 -1
  91. package/dist/src/types/zod/queue.d.ts +38 -38
  92. package/dist/src/types/zod/strict-keys.test.d.ts +2 -0
  93. package/dist/src/types/zod/strict-keys.test.d.ts.map +1 -0
  94. package/dist/tsconfig.tsbuildinfo +1 -1
  95. package/package.json +2 -2
  96. package/src/analyzeQuery/resolveVariables.ts +37 -7
  97. package/src/authentication/jwt.ts +3 -1
  98. package/src/authentication/paseto.ts +3 -1
  99. package/src/authentication/tokenRepository.ts +12 -4
  100. package/src/config/helpers/operationHelper.ts +146 -5
  101. package/src/config/index.ts +139 -62
  102. package/src/config/types/ai.ts +2 -2
  103. package/src/config/types/auth.ts +37 -6
  104. package/src/config/types/cron.ts +1 -1
  105. package/src/config/types/db.ts +10 -10
  106. package/src/config/types/index.ts +2 -0
  107. package/src/config/types/operation.ts +49 -9
  108. package/src/config/types/queue.ts +8 -8
  109. package/src/config/types/remote-rest.ts +1 -1
  110. package/src/config/types/remote-schema.ts +2 -2
  111. package/src/config/types/virtual-columns.ts +1 -1
  112. package/src/configuration/getSchemas/mergeEntities/index.ts +32 -1
  113. package/src/configuration/getSchemas/type-definition-generator/index.ts +23 -9
  114. package/src/configuration/rest/generateOpenAPI.ts +5 -9
  115. package/src/configuration/rest/handleRESTRequestFactory.ts +38 -14
  116. package/src/configuration/rest/parseStringParams.ts +92 -0
  117. package/src/databases/common.ts +163 -66
  118. package/src/databases/directives.ts +26 -8
  119. package/src/databases/engines/mssql/connection.ts +1 -1
  120. package/src/databases/engines/mssql/query/index.ts +47 -16
  121. package/src/databases/engines/mysql/auth.ts +2 -2
  122. package/src/databases/engines/mysql/connection.ts +5 -4
  123. package/src/databases/engines/mysql/getStructure.ts +61 -30
  124. package/src/databases/engines/mysql/placeholders.ts +84 -0
  125. package/src/databases/engines/mysql/query/index.ts +68 -10
  126. package/src/databases/engines/postgresql/query/index.ts +59 -7
  127. package/src/databases/high-level-operations.ts +2 -2
  128. package/src/databases/sqlTypeUtils.ts +3 -1
  129. package/src/databases/transformers/genResolverName.ts +3 -3
  130. package/src/databases/transformers/graphqlName.ts +28 -0
  131. package/src/subscriptions/strategies/index.ts +1 -1
  132. package/src/subscriptions/strategies/queue.ts +1 -1
  133. package/src/types/resolver.ts +17 -1
  134. package/src/types/zod/configuration.ts +1 -1
  135. package/src/types/zod/db.ts +4 -0
@@ -16,22 +16,28 @@ import {
16
16
  isAggregationField,
17
17
  isSingleQuery,
18
18
  processFieldSelectionsPG,
19
+ sqlColumnName,
19
20
  } from "../../../common";
20
21
 
22
+ import { applyDirectives } from "../../../directives";
23
+
21
24
  // Generate CTE for aggregations
22
25
  const buildAggregationCTE = (
26
+ entities: MergedEntities,
27
+ tableName: string,
23
28
  groupByInfo: GroupByInfo,
24
29
  dottedQuotedName: string,
25
30
  tableAlias: string,
26
31
  whereClause: string,
27
32
  ): string => {
28
33
  const { groupByFields, aggregations, cteAlias } = groupByInfo;
34
+ const sqlName = (fieldName: string) => sqlColumnName(entities, tableName, fieldName);
29
35
 
30
36
  const selectClauses: string[] = [];
31
37
 
32
38
  // Add group by fields
33
39
  groupByFields.forEach((field) => {
34
- selectClauses.push(`${tableAlias}."${field}"`);
40
+ selectClauses.push(`${tableAlias}."${sqlName(field)}"`);
35
41
  });
36
42
 
37
43
  // Add aggregations
@@ -40,11 +46,11 @@ const buildAggregationCTE = (
40
46
  selectClauses.push(`COUNT(*) AS ${agg.alias}`);
41
47
  } else {
42
48
  const func = agg.name.toUpperCase();
43
- selectClauses.push(`${func}(${tableAlias}."${agg.fieldName}") AS ${agg.alias}`);
49
+ selectClauses.push(`${func}(${tableAlias}."${sqlName(agg.fieldName)}") AS ${agg.alias}`);
44
50
  }
45
51
  });
46
52
 
47
- const groupByClause = `GROUP BY ${groupByFields.map((field) => `${tableAlias}."${field}"`).join(", ")}`;
53
+ const groupByClause = `GROUP BY ${groupByFields.map((field) => `${tableAlias}."${sqlName(field)}"`).join(", ")}`;
48
54
 
49
55
  return `${cteAlias} AS (
50
56
  SELECT
@@ -58,6 +64,7 @@ const buildAggregationCTE = (
58
64
  // Build the main query for grouped results
59
65
  const buildGroupedQuery = (
60
66
  entities: MergedEntities,
67
+ variablesDefinition: VariableDefinition[],
61
68
  field: SelectionAnalysis,
62
69
  groupByInfo: GroupByInfo,
63
70
  dottedQuotedName: string,
@@ -67,12 +74,22 @@ const buildGroupedQuery = (
67
74
  const { groupByFields, aggregations, hasItems, keyResolved, hasKey, keys, cteAlias } =
68
75
  groupByInfo;
69
76
 
77
+ const sqlName = (fieldName: string) => sqlColumnName(entities, field.name, fieldName);
78
+
70
79
  const selectClauses: string[] = [];
71
80
 
72
81
  if (hasKey) {
73
82
  // Add key object with group by fields
74
83
  const keyFields = keys
75
- .map((key) => `'${key.alias || key.name}', ${cteAlias}."${key.name}"`)
84
+ .map((key) => {
85
+ const selector = applyDirectives(
86
+ `${cteAlias}."${sqlName(key.name)}"`,
87
+ key.directives,
88
+ "pg",
89
+ variablesDefinition,
90
+ );
91
+ return `'${key.alias || key.name}', ${selector}`;
92
+ })
76
93
  .join(", ");
77
94
 
78
95
  selectClauses.push(`'${keyResolved}', json_build_object(${keyFields})`);
@@ -96,12 +113,21 @@ const buildGroupedQuery = (
96
113
  if (itemsSelection?.selections) {
97
114
  const itemFields = itemsSelection.selections
98
115
  .filter((sel) => !isAggregationField(sel.name) && sel.name !== "items")
99
- .map((sel) => `'${sel.alias || sel.name}', ${tableAlias}."${sel.name}"`)
116
+ .map((sel) => {
117
+ const selector = applyDirectives(
118
+ `${tableAlias}."${sqlName(sel.name)}"`,
119
+ sel.directives,
120
+ "pg",
121
+ variablesDefinition,
122
+ );
123
+ return `'${sel.alias || sel.name}', ${selector}`;
124
+ })
100
125
  .join(", ");
101
126
 
102
127
  if (itemFields) {
103
128
  const joinConditions = groupByFields.map(
104
- (field) => `${tableAlias}."${field}" = ${cteAlias}."${field}"`,
129
+ (groupByField) =>
130
+ `${tableAlias}."${sqlName(groupByField)}" = ${cteAlias}."${sqlName(groupByField)}"`,
105
131
  );
106
132
 
107
133
  const whereConditions = whereClause
@@ -171,7 +197,14 @@ export const generateSQL = (
171
197
  {},
172
198
  );
173
199
 
174
- const cte = buildAggregationCTE(groupByInfo, dottedQuotedName, tableAlias, whereClause);
200
+ const cte = buildAggregationCTE(
201
+ entities,
202
+ field.name,
203
+ groupByInfo,
204
+ dottedQuotedName,
205
+ tableAlias,
206
+ whereClause,
207
+ );
175
208
  ctes.push(cte);
176
209
  }
177
210
 
@@ -240,6 +273,7 @@ export const buildSQLForField = (
240
273
  // Handle GROUP BY aggregation query
241
274
  const mainQuery = buildGroupedQuery(
242
275
  entities,
276
+ variablesDefinition,
243
277
  field,
244
278
  groupByInfo,
245
279
  dottedQuotedName,
@@ -280,6 +314,24 @@ export const buildSQLForField = (
280
314
 
281
315
  const isArraySelection = !!field.isArray && !withoutArrayWrapper;
282
316
 
317
+ // json_agg collapses the rows into one, so LIMIT/OFFSET applied next to it
318
+ // trims aggregate rows rather than table rows: the limit does nothing and any
319
+ // non-zero offset skips the only row there is. Paginate inside a derived table
320
+ // and aggregate that. ROW_NUMBER over the same ordering carries the order out
321
+ // to the aggregate, which PostgreSQL does not otherwise guarantee.
322
+ if (isArraySelection && paginationClause) {
323
+ const pageAlias = `${tableAlias}_page`;
324
+
325
+ return `
326
+ COALESCE((
327
+ SELECT json_agg(${pageAlias}.obj ORDER BY ${pageAlias}.__ord)
328
+ FROM (
329
+ SELECT json_build_object(${selectList}) AS obj, ROW_NUMBER() OVER (${orderByClause}) AS __ord ${fromClause} ${whereClause} ${orderByClause} ${paginationClause}
330
+ ) ${pageAlias}
331
+ ), '[]'::json)
332
+ `;
333
+ }
334
+
283
335
  return `
284
336
  COALESCE((
285
337
  SELECT ${isArraySelection ? `json_agg(json_build_object(${selectList}) ${orderByClause})` : `json_build_object(${selectList})`} ${fromClause} ${whereClause} ${withoutArrayWrapper ? " LIMIT 1" : ""} ${paginationClause ? ` ${paginationClause}` : ""}
@@ -11,7 +11,7 @@ import type {
11
11
  TableFilter,
12
12
  VirtualColumns,
13
13
  } from "../types/configuration";
14
- import type { Column, ProcedureResolver, TableResolver, Tables } from "../types/db";
14
+ import type { ProcedureResolver, TableColumn, TableResolver, Tables } from "../types/db";
15
15
 
16
16
  import { getDatabaseStructure } from "./metadata/structure";
17
17
  import { buildProcedureResolver, buildTableResolver } from "./transformers/data-transformers";
@@ -63,7 +63,7 @@ export const getDatabasesStructure = async (
63
63
 
64
64
  if (tableOverride.columns.length) {
65
65
  // Mix database columns with virtual columns from configuration
66
- t.columns = [...t.columns, ...tableOverride.columns] as Column[];
66
+ t.columns = [...t.columns, ...tableOverride.columns] as TableColumn[];
67
67
  }
68
68
 
69
69
  if (tableOverride.relationships.length) {
@@ -27,7 +27,6 @@ export const categorizeSqlType = (dataType: string): SqlTypeCategory => {
27
27
  lowerType === "bigint" ||
28
28
  lowerType === "tinyint" ||
29
29
  lowerType === "mediumint" ||
30
- lowerType === "numeric" ||
31
30
  lowerType === "number"
32
31
  ) {
33
32
  return SqlTypeCategory.INTEGER;
@@ -40,6 +39,9 @@ export const categorizeSqlType = (dataType: string): SqlTypeCategory => {
40
39
  lowerType === "double" ||
41
40
  lowerType === "double precision" ||
42
41
  lowerType === "decimal" ||
42
+ // PostgreSQL's information_schema reports DECIMAL(p, s) as "numeric", so
43
+ // classifying it as an integer typed every PG decimal column as GraphQL Int.
44
+ lowerType === "numeric" ||
43
45
  lowerType === "money" ||
44
46
  lowerType === "smallmoney"
45
47
  ) {
@@ -1,3 +1,5 @@
1
+ import { sanitizeGraphQLName } from "./graphqlName";
2
+
1
3
  export const genResolverName = (
2
4
  schema: string,
3
5
  name: string,
@@ -12,7 +14,5 @@ export const genResolverName = (
12
14
  .replace("{schema}", schema)
13
15
  .replace("{name}", name);
14
16
 
15
- if (suffix) return `${baseName}_${suffix}`;
16
-
17
- return baseName;
17
+ return sanitizeGraphQLName(suffix ? `${baseName}_${suffix}` : baseName);
18
18
  };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Maps a SQL identifier onto a legal GraphQL name (`/^[_A-Za-z][_0-9A-Za-z]*$/`).
3
+ *
4
+ * PostgreSQL, MySQL and SQL Server all accept identifiers GraphQL cannot spell —
5
+ * spaces, accents, punctuation — and an unmapped one produces an SDL document
6
+ * that fails to parse, taking the whole server down at boot.
7
+ *
8
+ * A name that is already legal is returned unchanged, so this cannot rename a
9
+ * field in any schema that boots today. Runs of illegal characters are not
10
+ * collapsed, and diacritics fold to their base letter rather than being dropped,
11
+ * both so that distinct identifiers stay distinct as often as possible.
12
+ */
13
+ export const sanitizeGraphQLName = (name: string): string => {
14
+ const folded = name.normalize("NFD").replace(/[̀-ͯ]/g, "");
15
+ const replaced = folded.replace(/[^_0-9A-Za-z]/g, "_");
16
+
17
+ if (replaced.length === 0) return "_";
18
+
19
+ return /^[0-9]/.test(replaced) ? `_${replaced}` : replaced;
20
+ };
21
+
22
+ /**
23
+ * The GraphQL field a column is exposed as. Introspected columns carry a
24
+ * `fieldName` computed once at parse time; virtual columns come from
25
+ * configuration and carry only a name, so they are sanitised on the spot.
26
+ */
27
+ export const columnFieldName = (column: { name: string; fieldName?: string }): string =>
28
+ column.fieldName ?? sanitizeGraphQLName(column.name);
@@ -14,7 +14,7 @@ export const createSubscriptionStrategyRegistry = (): SubscriptionStrategyRegist
14
14
  const queueStrategy = createQueueSubscriptionStrategy();
15
15
 
16
16
  registry.set(EntitySource.TABLE, databaseStrategy);
17
- registry.set(EntitySource.QUEUE_PUBLISHER, queueStrategy);
17
+ registry.set(EntitySource.QUEUE_SUBSCRIBER, queueStrategy);
18
18
 
19
19
  return registry;
20
20
  };
@@ -23,7 +23,7 @@ const buildCompositeKey = (context: SubscriptionContext): string => {
23
23
  * - This strategy manages connection registration with the event emitter
24
24
  */
25
25
  export const createQueueSubscriptionStrategy = (): SubscriptionStrategy => ({
26
- source: EntitySource.QUEUE_PUBLISHER,
26
+ source: EntitySource.QUEUE_SUBSCRIBER,
27
27
 
28
28
  getSubscriptionKey(context) {
29
29
  return buildCompositeKey(context);
@@ -1,6 +1,6 @@
1
1
  import type { RemoteRESTResolved, RemoteRESTRoute } from "../remoteREST/types";
2
2
  import type { RemoteSchemaResolved } from "../remoteSchemas/types";
3
- import type { Publisher } from "./configuration";
3
+ import type { Publisher, Subscriber } from "./configuration";
4
4
  import type { ProcedureResolver, TableResolver } from "./db";
5
5
  import type { TypedOperation } from "./zod/operation";
6
6
 
@@ -14,6 +14,8 @@ export enum EntitySource {
14
14
  STORED_PROCEDURE = "stored_procedure",
15
15
  /** Message queue (RabbitMQ, Kafka, etc.) */
16
16
  QUEUE_PUBLISHER = "queue_publisher",
17
+ /** Message queue subscriber (exposed as a GraphQL subscription) */
18
+ QUEUE_SUBSCRIBER = "queue_subscriber",
17
19
  /** Authentication mutations (login, refresh, etc.) */
18
20
  AUTH = "auth",
19
21
  /** Custom operation handlers */
@@ -57,6 +59,14 @@ export type QueueResolverEntry = ResolverEntryBase & {
57
59
  resolver: Publisher;
58
60
  };
59
61
 
62
+ /**
63
+ * Resolver entry for message queue subscribers
64
+ */
65
+ export type QueueSubscriberResolverEntry = ResolverEntryBase & {
66
+ source: EntitySource.QUEUE_SUBSCRIBER;
67
+ resolver: Subscriber;
68
+ };
69
+
60
70
  /**
61
71
  * Resolver entry for authentication operations
62
72
  */
@@ -114,6 +124,7 @@ export type ResolverEntry =
114
124
  | TableResolverEntry
115
125
  | StoredProcedureResolverEntry
116
126
  | QueueResolverEntry
127
+ | QueueSubscriberResolverEntry
117
128
  | AuthResolverEntry
118
129
  | OperationResolverEntry
119
130
  | RemoteSchemaResolverEntry
@@ -144,6 +155,11 @@ export const createResolverEntry = {
144
155
  resolver,
145
156
  }),
146
157
 
158
+ queueSubscriber: (resolver: Subscriber): QueueSubscriberResolverEntry => ({
159
+ source: EntitySource.QUEUE_SUBSCRIBER,
160
+ resolver,
161
+ }),
162
+
147
163
  auth: (
148
164
  name: string,
149
165
  operation: AuthResolverEntry["resolver"]["operation"],
@@ -11,7 +11,7 @@ import { RemoteRESTConfigZod } from "./remoteREST";
11
11
  import { RemoteSchemaConfigZod } from "./remoteSchema";
12
12
 
13
13
  export const ConfigurationZod = z
14
- .object({
14
+ .strictObject({
15
15
  name: z.string(),
16
16
  version: z.string(),
17
17
  tokenStrategy: TokenStrategyZod.optional().default("jwt"),
@@ -8,6 +8,7 @@ import {
8
8
  TableRelationshipZod as TableRelationshipBaseZod,
9
9
  } from "../../config";
10
10
  import { genResolverName } from "../../databases/transformers/genResolverName";
11
+ import { sanitizeGraphQLName } from "../../databases/transformers/graphqlName";
11
12
 
12
13
  // Re-export base types and schemas from the config module
13
14
  export type {
@@ -43,6 +44,9 @@ export const TableColumnZod = z
43
44
  })
44
45
  .transform((val) => ({
45
46
  name: val.name,
47
+ // The SQL identifier stays in `name`; `fieldName` is what GraphQL exposes it as.
48
+ // Nothing emitting SQL may read `fieldName`, nothing emitting SDL may read `name`.
49
+ fieldName: sanitizeGraphQLName(val.name),
46
50
  dataType: val.dataType,
47
51
  isNullable: typeof val.isNullable === "boolean" ? val.isNullable : val.isNullable === 1,
48
52
  description: val.description ?? null,