@constructive-io/graphql-query 3.2.5 → 3.3.0

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 (88) hide show
  1. package/README.md +411 -65
  2. package/ast.d.ts +4 -4
  3. package/ast.js +24 -9
  4. package/client/error.d.ts +95 -0
  5. package/client/error.js +277 -0
  6. package/client/execute.d.ts +57 -0
  7. package/client/execute.js +124 -0
  8. package/client/index.d.ts +8 -0
  9. package/client/index.js +20 -0
  10. package/client/typed-document.d.ts +31 -0
  11. package/client/typed-document.js +44 -0
  12. package/custom-ast.d.ts +22 -8
  13. package/custom-ast.js +16 -1
  14. package/esm/ast.js +22 -7
  15. package/esm/client/error.js +271 -0
  16. package/esm/client/execute.js +120 -0
  17. package/esm/client/index.js +8 -0
  18. package/esm/client/typed-document.js +40 -0
  19. package/esm/custom-ast.js +16 -1
  20. package/esm/generators/field-selector.js +381 -0
  21. package/esm/generators/index.js +13 -0
  22. package/esm/generators/mutations.js +200 -0
  23. package/esm/generators/naming-helpers.js +154 -0
  24. package/esm/generators/select.js +661 -0
  25. package/esm/index.js +30 -0
  26. package/esm/introspect/index.js +9 -0
  27. package/esm/introspect/infer-tables.js +697 -0
  28. package/esm/introspect/schema-query.js +120 -0
  29. package/esm/introspect/transform-schema.js +271 -0
  30. package/esm/introspect/transform.js +38 -0
  31. package/esm/meta-object/convert.js +3 -0
  32. package/esm/meta-object/format.json +11 -41
  33. package/esm/meta-object/validate.js +20 -4
  34. package/esm/query-builder.js +14 -18
  35. package/esm/types/index.js +18 -0
  36. package/esm/types/introspection.js +54 -0
  37. package/esm/types/mutation.js +4 -0
  38. package/esm/types/query.js +4 -0
  39. package/esm/types/schema.js +5 -0
  40. package/esm/types/selection.js +4 -0
  41. package/esm/utils.js +69 -0
  42. package/generators/field-selector.d.ts +30 -0
  43. package/generators/field-selector.js +387 -0
  44. package/generators/index.d.ts +9 -0
  45. package/generators/index.js +42 -0
  46. package/generators/mutations.d.ts +30 -0
  47. package/generators/mutations.js +238 -0
  48. package/generators/naming-helpers.d.ts +48 -0
  49. package/generators/naming-helpers.js +169 -0
  50. package/generators/select.d.ts +39 -0
  51. package/generators/select.js +705 -0
  52. package/index.d.ts +19 -0
  53. package/index.js +34 -1
  54. package/introspect/index.d.ts +9 -0
  55. package/introspect/index.js +25 -0
  56. package/introspect/infer-tables.d.ts +42 -0
  57. package/introspect/infer-tables.js +700 -0
  58. package/introspect/schema-query.d.ts +20 -0
  59. package/introspect/schema-query.js +123 -0
  60. package/introspect/transform-schema.d.ts +86 -0
  61. package/introspect/transform-schema.js +281 -0
  62. package/introspect/transform.d.ts +20 -0
  63. package/introspect/transform.js +43 -0
  64. package/meta-object/convert.d.ts +3 -0
  65. package/meta-object/convert.js +3 -0
  66. package/meta-object/format.json +11 -41
  67. package/meta-object/validate.d.ts +8 -3
  68. package/meta-object/validate.js +20 -4
  69. package/package.json +4 -3
  70. package/query-builder.d.ts +11 -12
  71. package/query-builder.js +25 -29
  72. package/{types.d.ts → types/core.d.ts} +25 -18
  73. package/types/index.d.ts +12 -0
  74. package/types/index.js +34 -0
  75. package/types/introspection.d.ts +121 -0
  76. package/types/introspection.js +62 -0
  77. package/types/mutation.d.ts +45 -0
  78. package/types/mutation.js +5 -0
  79. package/types/query.d.ts +91 -0
  80. package/types/query.js +5 -0
  81. package/types/schema.d.ts +265 -0
  82. package/types/schema.js +6 -0
  83. package/types/selection.d.ts +43 -0
  84. package/types/selection.js +5 -0
  85. package/utils.d.ts +17 -0
  86. package/utils.js +72 -0
  87. /package/esm/{types.js → types/core.js} +0 -0
  88. /package/{types.js → types/core.js} +0 -0
@@ -0,0 +1,705 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.toOrderByTypeName = exports.toCamelCasePlural = void 0;
37
+ exports.cleanTableToMetaObject = cleanTableToMetaObject;
38
+ exports.generateIntrospectionSchema = generateIntrospectionSchema;
39
+ exports.createASTQueryBuilder = createASTQueryBuilder;
40
+ exports.buildSelect = buildSelect;
41
+ exports.buildFindOne = buildFindOne;
42
+ exports.buildCount = buildCount;
43
+ /**
44
+ * Query generators for SELECT, FindOne, and Count operations
45
+ * Uses AST-based approach for all query generation
46
+ */
47
+ const t = __importStar(require("gql-ast"));
48
+ const graphql_1 = require("graphql");
49
+ const typed_document_1 = require("../client/typed-document");
50
+ const custom_ast_1 = require("../custom-ast");
51
+ const query_builder_1 = require("../query-builder");
52
+ const field_selector_1 = require("./field-selector");
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; } });
58
+ /**
59
+ * Convert CleanTable to MetaObject format for QueryBuilder
60
+ */
61
+ function cleanTableToMetaObject(tables) {
62
+ return {
63
+ tables: tables.map((table) => ({
64
+ name: table.name,
65
+ fields: table.fields.map((field) => ({
66
+ name: field.name,
67
+ type: {
68
+ gqlType: field.type.gqlType,
69
+ isArray: field.type.isArray,
70
+ modifier: field.type.modifier,
71
+ pgAlias: field.type.pgAlias,
72
+ pgType: field.type.pgType,
73
+ subtype: field.type.subtype,
74
+ typmod: field.type.typmod,
75
+ },
76
+ })),
77
+ primaryConstraints: [], // Would need to be derived from schema
78
+ uniqueConstraints: [], // Would need to be derived from schema
79
+ foreignConstraints: table.relations.belongsTo.map((rel) => ({
80
+ refTable: rel.referencesTable,
81
+ fromKey: {
82
+ name: rel.fieldName || '',
83
+ type: {
84
+ gqlType: 'UUID', // Default, should be derived from actual field
85
+ isArray: false,
86
+ modifier: null,
87
+ pgAlias: null,
88
+ pgType: null,
89
+ subtype: null,
90
+ typmod: null,
91
+ },
92
+ alias: rel.fieldName || '',
93
+ },
94
+ toKey: {
95
+ name: 'id',
96
+ type: {
97
+ gqlType: 'UUID',
98
+ isArray: false,
99
+ modifier: null,
100
+ pgAlias: null,
101
+ pgType: null,
102
+ subtype: null,
103
+ typmod: null,
104
+ },
105
+ },
106
+ })),
107
+ })),
108
+ };
109
+ }
110
+ /**
111
+ * Generate basic IntrospectionSchema from CleanTable array
112
+ * This creates a minimal schema for AST generation
113
+ */
114
+ function generateIntrospectionSchema(tables) {
115
+ const schema = {};
116
+ for (const table of tables) {
117
+ const modelName = table.name;
118
+ const pluralName = (0, naming_helpers_1.toCamelCasePlural)(modelName, table);
119
+ // Basic field selection for the model
120
+ const selection = table.fields.map((field) => field.name);
121
+ // Add getMany query
122
+ schema[pluralName] = {
123
+ qtype: 'getMany',
124
+ model: modelName,
125
+ selection,
126
+ properties: convertFieldsToProperties(table.fields),
127
+ };
128
+ // Add getOne query (by ID)
129
+ const singularName = (0, naming_helpers_1.toCamelCaseSingular)(modelName, table);
130
+ schema[singularName] = {
131
+ qtype: 'getOne',
132
+ model: modelName,
133
+ selection,
134
+ properties: convertFieldsToProperties(table.fields),
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`;
144
+ // Add create mutation
145
+ schema[createMutationName] = {
146
+ qtype: 'mutation',
147
+ mutationType: 'create',
148
+ model: modelName,
149
+ selection,
150
+ properties: {
151
+ input: {
152
+ name: 'input',
153
+ type: createInputType,
154
+ isNotNull: true,
155
+ isArray: false,
156
+ isArrayNotNull: false,
157
+ properties: {
158
+ [singularName]: {
159
+ name: singularName,
160
+ type: `${modelName}Input`,
161
+ isNotNull: true,
162
+ isArray: false,
163
+ isArrayNotNull: false,
164
+ properties: convertFieldsToNestedProperties(table.fields),
165
+ },
166
+ },
167
+ },
168
+ },
169
+ };
170
+ // Add update mutation
171
+ schema[updateMutationName] = {
172
+ qtype: 'mutation',
173
+ mutationType: 'patch',
174
+ model: modelName,
175
+ selection,
176
+ properties: {
177
+ input: {
178
+ name: 'input',
179
+ type: (0, naming_helpers_1.toUpdateInputTypeName)(modelName),
180
+ isNotNull: true,
181
+ isArray: false,
182
+ isArrayNotNull: false,
183
+ properties: {
184
+ [patchFieldName]: {
185
+ name: patchFieldName,
186
+ type: patchType,
187
+ isNotNull: true,
188
+ isArray: false,
189
+ isArrayNotNull: false,
190
+ properties: convertFieldsToNestedProperties(table.fields),
191
+ },
192
+ },
193
+ },
194
+ },
195
+ };
196
+ // Add delete mutation
197
+ schema[deleteMutationName] = {
198
+ qtype: 'mutation',
199
+ mutationType: 'delete',
200
+ model: modelName,
201
+ selection,
202
+ properties: {
203
+ input: {
204
+ name: 'input',
205
+ type: (0, naming_helpers_1.toDeleteInputTypeName)(modelName),
206
+ isNotNull: true,
207
+ isArray: false,
208
+ isArrayNotNull: false,
209
+ properties: {
210
+ id: {
211
+ name: 'id',
212
+ type: 'UUID',
213
+ isNotNull: true,
214
+ isArray: false,
215
+ isArrayNotNull: false,
216
+ },
217
+ },
218
+ },
219
+ },
220
+ };
221
+ }
222
+ return schema;
223
+ }
224
+ /**
225
+ * Convert CleanTable fields to QueryBuilder properties
226
+ */
227
+ function convertFieldsToProperties(fields) {
228
+ const properties = {};
229
+ fields.forEach((field) => {
230
+ properties[field.name] = {
231
+ name: field.name,
232
+ type: field.type.gqlType,
233
+ isNotNull: !field.type.gqlType.endsWith('!'),
234
+ isArray: field.type.isArray,
235
+ isArrayNotNull: false,
236
+ };
237
+ });
238
+ return properties;
239
+ }
240
+ /**
241
+ * Convert fields to nested properties for mutations
242
+ */
243
+ function convertFieldsToNestedProperties(fields) {
244
+ const properties = {};
245
+ fields.forEach((field) => {
246
+ properties[field.name] = {
247
+ name: field.name,
248
+ type: field.type.gqlType,
249
+ isNotNull: false, // Mutations typically allow optional fields
250
+ isArray: field.type.isArray,
251
+ isArrayNotNull: false,
252
+ };
253
+ });
254
+ return properties;
255
+ }
256
+ /**
257
+ * Create AST-based query builder for a table
258
+ */
259
+ function createASTQueryBuilder(tables) {
260
+ const metaObject = cleanTableToMetaObject(tables);
261
+ const introspectionSchema = generateIntrospectionSchema(tables);
262
+ return new query_builder_1.QueryBuilder({
263
+ meta: metaObject,
264
+ introspection: introspectionSchema,
265
+ });
266
+ }
267
+ /**
268
+ * Build a SELECT query for a table with optional filtering, sorting, and pagination
269
+ * Uses direct AST generation without intermediate conversions
270
+ */
271
+ function buildSelect(table, allTables, options = {}) {
272
+ const tableList = Array.from(allTables);
273
+ const selection = convertFieldSelectionToSelectionOptions(table, tableList, options.fieldSelection);
274
+ // Generate query directly using AST
275
+ const queryString = generateSelectQueryAST(table, tableList, selection, options, options.relationFieldMap);
276
+ return new typed_document_1.TypedDocumentString(queryString, {});
277
+ }
278
+ /**
279
+ * Build a single row query by primary key or unique field
280
+ */
281
+ function buildFindOne(table, _pkField = 'id') {
282
+ const queryString = generateFindOneQueryAST(table);
283
+ return new typed_document_1.TypedDocumentString(queryString, {});
284
+ }
285
+ /**
286
+ * Build a count query for a table
287
+ */
288
+ function buildCount(table) {
289
+ const queryString = generateCountQueryAST(table);
290
+ return new typed_document_1.TypedDocumentString(queryString, {});
291
+ }
292
+ function convertFieldSelectionToSelectionOptions(table, allTables, options) {
293
+ return (0, field_selector_1.convertToSelectionOptions)(table, allTables, options);
294
+ }
295
+ /**
296
+ * Generate SELECT query AST directly from CleanTable
297
+ */
298
+ function generateSelectQueryAST(table, allTables, selection, options, relationFieldMap) {
299
+ const pluralName = (0, naming_helpers_1.toCamelCasePlural)(table.name, table);
300
+ // Generate field selections
301
+ const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap);
302
+ // Build the query AST
303
+ const variableDefinitions = [];
304
+ const queryArgs = [];
305
+ // Add pagination variables if needed
306
+ const limitValue = options.limit ?? options.first;
307
+ if (limitValue !== undefined) {
308
+ variableDefinitions.push(t.variableDefinition({
309
+ variable: t.variable({ name: 'first' }),
310
+ type: t.namedType({ type: 'Int' }),
311
+ }));
312
+ queryArgs.push(t.argument({
313
+ name: 'first',
314
+ value: t.variable({ name: 'first' }),
315
+ }));
316
+ }
317
+ if (options.offset !== undefined) {
318
+ variableDefinitions.push(t.variableDefinition({
319
+ variable: t.variable({ name: 'offset' }),
320
+ type: t.namedType({ type: 'Int' }),
321
+ }));
322
+ queryArgs.push(t.argument({
323
+ name: 'offset',
324
+ value: t.variable({ name: 'offset' }),
325
+ }));
326
+ }
327
+ // Add cursor-based pagination variables if needed (for infinite scroll)
328
+ if (options.after !== undefined) {
329
+ variableDefinitions.push(t.variableDefinition({
330
+ variable: t.variable({ name: 'after' }),
331
+ type: t.namedType({ type: 'Cursor' }),
332
+ }));
333
+ queryArgs.push(t.argument({
334
+ name: 'after',
335
+ value: t.variable({ name: 'after' }),
336
+ }));
337
+ }
338
+ if (options.before !== undefined) {
339
+ variableDefinitions.push(t.variableDefinition({
340
+ variable: t.variable({ name: 'before' }),
341
+ type: t.namedType({ type: 'Cursor' }),
342
+ }));
343
+ queryArgs.push(t.argument({
344
+ name: 'before',
345
+ value: t.variable({ name: 'before' }),
346
+ }));
347
+ }
348
+ // Add filter variables if needed
349
+ if (options.where) {
350
+ variableDefinitions.push(t.variableDefinition({
351
+ variable: t.variable({ name: 'filter' }),
352
+ type: t.namedType({ type: (0, naming_helpers_1.toFilterTypeName)(table.name, table) }),
353
+ }));
354
+ queryArgs.push(t.argument({
355
+ name: 'filter',
356
+ value: t.variable({ name: 'filter' }),
357
+ }));
358
+ }
359
+ // Add orderBy variables if needed
360
+ if (options.orderBy && options.orderBy.length > 0) {
361
+ variableDefinitions.push(t.variableDefinition({
362
+ variable: t.variable({ name: 'orderBy' }),
363
+ // PostGraphile expects [ProductsOrderBy!] - list of non-null enum values
364
+ type: t.listType({
365
+ type: t.nonNullType({
366
+ type: t.namedType({ type: (0, naming_helpers_1.toOrderByTypeName)(table.name, table) }),
367
+ }),
368
+ }),
369
+ }));
370
+ queryArgs.push(t.argument({
371
+ name: 'orderBy',
372
+ value: t.variable({ name: 'orderBy' }),
373
+ }));
374
+ }
375
+ // Build connection selections: totalCount, nodes, and optionally pageInfo
376
+ const connectionSelections = [
377
+ t.field({ name: 'totalCount' }),
378
+ t.field({
379
+ name: 'nodes',
380
+ selectionSet: t.selectionSet({
381
+ selections: fieldSelections,
382
+ }),
383
+ }),
384
+ ];
385
+ // Add pageInfo if requested (for cursor-based pagination / infinite scroll)
386
+ if (options.includePageInfo ||
387
+ options.after !== undefined ||
388
+ options.before !== undefined) {
389
+ connectionSelections.push(t.field({
390
+ name: 'pageInfo',
391
+ selectionSet: t.selectionSet({
392
+ selections: [
393
+ t.field({ name: 'hasNextPage' }),
394
+ t.field({ name: 'hasPreviousPage' }),
395
+ t.field({ name: 'startCursor' }),
396
+ t.field({ name: 'endCursor' }),
397
+ ],
398
+ }),
399
+ }));
400
+ }
401
+ const ast = t.document({
402
+ definitions: [
403
+ t.operationDefinition({
404
+ operation: graphql_1.OperationTypeNode.QUERY,
405
+ name: `${pluralName}Query`,
406
+ variableDefinitions,
407
+ selectionSet: t.selectionSet({
408
+ selections: [
409
+ t.field({
410
+ name: pluralName,
411
+ args: queryArgs,
412
+ selectionSet: t.selectionSet({
413
+ selections: connectionSelections,
414
+ }),
415
+ }),
416
+ ],
417
+ }),
418
+ }),
419
+ ],
420
+ });
421
+ return (0, graphql_1.print)(ast);
422
+ }
423
+ /**
424
+ * Generate field selections from SelectionOptions
425
+ */
426
+ function generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap) {
427
+ const DEFAULT_NESTED_RELATION_FIRST = 20;
428
+ if (!selection) {
429
+ // Default to all non-relational fields (includes complex fields like JSON, geometry, etc.)
430
+ return table.fields
431
+ .filter((field) => !(0, field_selector_1.isRelationalField)(field.name, table))
432
+ .map((field) => {
433
+ if ((0, custom_ast_1.requiresSubfieldSelection)(field)) {
434
+ // For complex fields that require subfield selection, use custom AST generation
435
+ return (0, custom_ast_1.getCustomAstForCleanField)(field);
436
+ }
437
+ else {
438
+ // For simple fields, use basic field selection
439
+ return t.field({ name: field.name });
440
+ }
441
+ });
442
+ }
443
+ const fieldSelections = [];
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
+ }
449
+ if (fieldOptions === true) {
450
+ // Check if this field requires subfield selection
451
+ const field = table.fields.find((f) => f.name === fieldName);
452
+ if (field && (0, custom_ast_1.requiresSubfieldSelection)(field)) {
453
+ // Use custom AST generation for complex fields
454
+ fieldSelections.push((0, custom_ast_1.getCustomAstForCleanField)(field));
455
+ }
456
+ else {
457
+ // Simple field selection for scalar fields
458
+ fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias));
459
+ }
460
+ }
461
+ else if (typeof fieldOptions === 'object' && fieldOptions.select) {
462
+ // Nested field selection (for relation fields)
463
+ const nestedSelections = [];
464
+ // Find the related table to check for complex fields
465
+ const relatedTable = findRelatedTable(fieldName, table, allTables);
466
+ Object.entries(fieldOptions.select).forEach(([nestedField, include]) => {
467
+ if (include) {
468
+ // Check if this nested field requires subfield selection
469
+ const nestedFieldDef = relatedTable?.fields.find((f) => f.name === nestedField);
470
+ if (nestedFieldDef && (0, custom_ast_1.requiresSubfieldSelection)(nestedFieldDef)) {
471
+ // Use custom AST generation for complex nested fields
472
+ nestedSelections.push((0, custom_ast_1.getCustomAstForCleanField)(nestedFieldDef));
473
+ }
474
+ else {
475
+ // Simple field selection for scalar nested fields
476
+ nestedSelections.push(t.field({ name: nestedField }));
477
+ }
478
+ }
479
+ });
480
+ // Check if this is a hasMany relation that uses Connection pattern
481
+ const relationInfo = getRelationInfo(fieldName, table);
482
+ if (relationInfo &&
483
+ (relationInfo.type === 'hasMany' || relationInfo.type === 'manyToMany')) {
484
+ // For hasMany/manyToMany relations, wrap selections in nodes { ... }
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
+ }),
499
+ }),
500
+ ],
501
+ })));
502
+ }
503
+ else {
504
+ // For belongsTo/hasOne relations, use direct selection
505
+ fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias, undefined, t.selectionSet({
506
+ selections: nestedSelections,
507
+ })));
508
+ }
509
+ }
510
+ });
511
+ return fieldSelections;
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
+ }
552
+ /**
553
+ * Get relation information for a field
554
+ */
555
+ function getRelationInfo(fieldName, table) {
556
+ const { belongsTo, hasOne, hasMany, manyToMany } = table.relations;
557
+ // Check belongsTo relations
558
+ const belongsToRel = belongsTo.find((rel) => rel.fieldName === fieldName);
559
+ if (belongsToRel) {
560
+ return { type: 'belongsTo', relation: belongsToRel };
561
+ }
562
+ // Check hasOne relations
563
+ const hasOneRel = hasOne.find((rel) => rel.fieldName === fieldName);
564
+ if (hasOneRel) {
565
+ return { type: 'hasOne', relation: hasOneRel };
566
+ }
567
+ // Check hasMany relations
568
+ const hasManyRel = hasMany.find((rel) => rel.fieldName === fieldName);
569
+ if (hasManyRel) {
570
+ return { type: 'hasMany', relation: hasManyRel };
571
+ }
572
+ // Check manyToMany relations
573
+ const manyToManyRel = manyToMany.find((rel) => rel.fieldName === fieldName);
574
+ if (manyToManyRel) {
575
+ return { type: 'manyToMany', relation: manyToManyRel };
576
+ }
577
+ return null;
578
+ }
579
+ /**
580
+ * Find the related table for a given relation field
581
+ */
582
+ function findRelatedTable(relationField, table, allTables) {
583
+ // Find the related table name
584
+ let referencedTableName;
585
+ // Check belongsTo relations
586
+ const belongsToRel = table.relations.belongsTo.find((rel) => rel.fieldName === relationField);
587
+ if (belongsToRel) {
588
+ referencedTableName = belongsToRel.referencesTable;
589
+ }
590
+ // Check hasOne relations
591
+ if (!referencedTableName) {
592
+ const hasOneRel = table.relations.hasOne.find((rel) => rel.fieldName === relationField);
593
+ if (hasOneRel) {
594
+ referencedTableName = hasOneRel.referencedByTable;
595
+ }
596
+ }
597
+ // Check hasMany relations
598
+ if (!referencedTableName) {
599
+ const hasManyRel = table.relations.hasMany.find((rel) => rel.fieldName === relationField);
600
+ if (hasManyRel) {
601
+ referencedTableName = hasManyRel.referencedByTable;
602
+ }
603
+ }
604
+ // Check manyToMany relations
605
+ if (!referencedTableName) {
606
+ const manyToManyRel = table.relations.manyToMany.find((rel) => rel.fieldName === relationField);
607
+ if (manyToManyRel) {
608
+ referencedTableName = manyToManyRel.rightTable;
609
+ }
610
+ }
611
+ if (!referencedTableName) {
612
+ return null;
613
+ }
614
+ // Find the related table in allTables
615
+ return allTables.find((tbl) => tbl.name === referencedTableName) || null;
616
+ }
617
+ /**
618
+ * Generate FindOne query AST directly from CleanTable
619
+ */
620
+ function generateFindOneQueryAST(table) {
621
+ const singularName = (0, naming_helpers_1.toCamelCaseSingular)(table.name, table);
622
+ // Generate field selections (include all non-relational fields, including complex types)
623
+ const fieldSelections = table.fields
624
+ .filter((field) => !(0, field_selector_1.isRelationalField)(field.name, table))
625
+ .map((field) => {
626
+ if ((0, custom_ast_1.requiresSubfieldSelection)(field)) {
627
+ // For complex fields that require subfield selection, use custom AST generation
628
+ return (0, custom_ast_1.getCustomAstForCleanField)(field);
629
+ }
630
+ else {
631
+ // For simple fields, use basic field selection
632
+ return t.field({ name: field.name });
633
+ }
634
+ });
635
+ const ast = t.document({
636
+ definitions: [
637
+ t.operationDefinition({
638
+ operation: graphql_1.OperationTypeNode.QUERY,
639
+ name: `${singularName}Query`,
640
+ variableDefinitions: [
641
+ t.variableDefinition({
642
+ variable: t.variable({ name: 'id' }),
643
+ type: t.nonNullType({
644
+ type: t.namedType({ type: 'UUID' }),
645
+ }),
646
+ }),
647
+ ],
648
+ selectionSet: t.selectionSet({
649
+ selections: [
650
+ t.field({
651
+ name: singularName,
652
+ args: [
653
+ t.argument({
654
+ name: 'id',
655
+ value: t.variable({ name: 'id' }),
656
+ }),
657
+ ],
658
+ selectionSet: t.selectionSet({
659
+ selections: fieldSelections,
660
+ }),
661
+ }),
662
+ ],
663
+ }),
664
+ }),
665
+ ],
666
+ });
667
+ return (0, graphql_1.print)(ast);
668
+ }
669
+ /**
670
+ * Generate Count query AST directly from CleanTable
671
+ */
672
+ function generateCountQueryAST(table) {
673
+ const pluralName = (0, naming_helpers_1.toCamelCasePlural)(table.name, table);
674
+ const ast = t.document({
675
+ definitions: [
676
+ t.operationDefinition({
677
+ operation: graphql_1.OperationTypeNode.QUERY,
678
+ name: `${pluralName}CountQuery`,
679
+ variableDefinitions: [
680
+ t.variableDefinition({
681
+ variable: t.variable({ name: 'filter' }),
682
+ type: t.namedType({ type: (0, naming_helpers_1.toFilterTypeName)(table.name, table) }),
683
+ }),
684
+ ],
685
+ selectionSet: t.selectionSet({
686
+ selections: [
687
+ t.field({
688
+ name: pluralName,
689
+ args: [
690
+ t.argument({
691
+ name: 'filter',
692
+ value: t.variable({ name: 'filter' }),
693
+ }),
694
+ ],
695
+ selectionSet: t.selectionSet({
696
+ selections: [t.field({ name: 'totalCount' })],
697
+ }),
698
+ }),
699
+ ],
700
+ }),
701
+ }),
702
+ ],
703
+ });
704
+ return (0, graphql_1.print)(ast);
705
+ }