@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,381 @@
1
+ const relationalFieldSetCache = new WeakMap();
2
+ function getRelationalFieldSet(table) {
3
+ const cached = relationalFieldSetCache.get(table);
4
+ if (cached)
5
+ return cached;
6
+ const set = new Set();
7
+ for (const rel of table.relations.belongsTo) {
8
+ if (rel.fieldName)
9
+ set.add(rel.fieldName);
10
+ }
11
+ for (const rel of table.relations.hasOne) {
12
+ if (rel.fieldName)
13
+ set.add(rel.fieldName);
14
+ }
15
+ for (const rel of table.relations.hasMany) {
16
+ if (rel.fieldName)
17
+ set.add(rel.fieldName);
18
+ }
19
+ for (const rel of table.relations.manyToMany) {
20
+ if (rel.fieldName)
21
+ set.add(rel.fieldName);
22
+ }
23
+ relationalFieldSetCache.set(table, set);
24
+ return set;
25
+ }
26
+ /**
27
+ * Convert simplified field selection to QueryBuilder SelectionOptions
28
+ */
29
+ export function convertToSelectionOptions(table, allTables, selection) {
30
+ if (!selection) {
31
+ return convertPresetToSelection(table, 'display');
32
+ }
33
+ if (typeof selection === 'string') {
34
+ return convertPresetToSelection(table, selection);
35
+ }
36
+ return convertCustomSelectionToOptions(table, allTables, selection);
37
+ }
38
+ /**
39
+ * Convert preset to selection options
40
+ */
41
+ function convertPresetToSelection(table, preset) {
42
+ const options = {};
43
+ switch (preset) {
44
+ case 'minimal': {
45
+ // Just id and first display field
46
+ const minimalFields = getMinimalFields(table);
47
+ minimalFields.forEach((field) => {
48
+ options[field] = true;
49
+ });
50
+ break;
51
+ }
52
+ case 'display': {
53
+ // Common display fields
54
+ const displayFields = getDisplayFields(table);
55
+ displayFields.forEach((field) => {
56
+ options[field] = true;
57
+ });
58
+ break;
59
+ }
60
+ case 'all': {
61
+ // All non-relational fields (includes complex fields like JSON, geometry, etc.)
62
+ const allFields = getNonRelationalFields(table);
63
+ allFields.forEach((field) => {
64
+ options[field] = true;
65
+ });
66
+ break;
67
+ }
68
+ case 'full':
69
+ // All fields including basic relations
70
+ table.fields.forEach((field) => {
71
+ options[field.name] = true;
72
+ });
73
+ break;
74
+ default: {
75
+ // Default to display
76
+ const defaultFields = getDisplayFields(table);
77
+ defaultFields.forEach((field) => {
78
+ options[field] = true;
79
+ });
80
+ }
81
+ }
82
+ return options;
83
+ }
84
+ /**
85
+ * Convert custom selection to options
86
+ */
87
+ function convertCustomSelectionToOptions(table, allTables, selection) {
88
+ const options = {};
89
+ // Start with selected fields or all non-relational fields (including complex types)
90
+ let fieldsToInclude;
91
+ if (selection.select) {
92
+ fieldsToInclude = selection.select;
93
+ }
94
+ else {
95
+ fieldsToInclude = getNonRelationalFields(table);
96
+ }
97
+ // Add basic fields
98
+ fieldsToInclude.forEach((field) => {
99
+ if (table.fields.some((f) => f.name === field)) {
100
+ options[field] = true;
101
+ }
102
+ });
103
+ // Handle includeRelations (simple API for relation fields)
104
+ if (selection.includeRelations) {
105
+ selection.includeRelations.forEach((relationField) => {
106
+ if (isRelationalField(relationField, table)) {
107
+ // Include with dynamically determined scalar fields from the related table
108
+ options[relationField] = {
109
+ select: getRelatedTableScalarFields(relationField, table, allTables),
110
+ variables: {},
111
+ };
112
+ }
113
+ });
114
+ }
115
+ // Handle includes (relations) - more detailed API
116
+ if (selection.include) {
117
+ Object.entries(selection.include).forEach(([relationField, relationSelection]) => {
118
+ if (isRelationalField(relationField, table)) {
119
+ if (relationSelection === true) {
120
+ // Include with dynamically determined scalar fields from the related table
121
+ options[relationField] = {
122
+ select: getRelatedTableScalarFields(relationField, table, allTables),
123
+ variables: {},
124
+ };
125
+ }
126
+ else if (Array.isArray(relationSelection)) {
127
+ // Include with specific fields
128
+ const selectObj = {};
129
+ relationSelection.forEach((field) => {
130
+ selectObj[field] = true;
131
+ });
132
+ options[relationField] = {
133
+ select: selectObj,
134
+ variables: {},
135
+ };
136
+ }
137
+ }
138
+ });
139
+ }
140
+ // Handle excludes
141
+ if (selection.exclude) {
142
+ selection.exclude.forEach((field) => {
143
+ delete options[field];
144
+ });
145
+ }
146
+ return options;
147
+ }
148
+ /**
149
+ * Get minimal fields - completely schema-driven, no hardcoded assumptions
150
+ */
151
+ function getMinimalFields(table) {
152
+ // Get all non-relational fields from the actual schema
153
+ const nonRelationalFields = getNonRelationalFields(table);
154
+ // Return the first few fields from the schema (typically includes primary key and basic fields)
155
+ // This is completely dynamic based on what the schema actually provides
156
+ return nonRelationalFields.slice(0, 3); // Limit to first 3 fields for minimal selection
157
+ }
158
+ /**
159
+ * Get display fields - completely schema-driven, no hardcoded field names
160
+ */
161
+ function getDisplayFields(table) {
162
+ // Get all non-relational fields from the actual schema
163
+ const nonRelationalFields = getNonRelationalFields(table);
164
+ // Return a reasonable subset for display purposes (first half of available fields)
165
+ // This is completely dynamic based on what the schema actually provides
166
+ const maxDisplayFields = Math.max(5, Math.floor(nonRelationalFields.length / 2));
167
+ return nonRelationalFields.slice(0, maxDisplayFields);
168
+ }
169
+ /**
170
+ * Get all non-relational fields (includes both scalar and complex fields)
171
+ * Complex fields like JSON, geometry, images should be included by default
172
+ */
173
+ function getNonRelationalFields(table) {
174
+ return table.fields
175
+ .filter((field) => !isRelationalField(field.name, table))
176
+ .map((field) => field.name);
177
+ }
178
+ /**
179
+ * Check if a field is relational using table metadata
180
+ */
181
+ export function isRelationalField(fieldName, table) {
182
+ return getRelationalFieldSet(table).has(fieldName);
183
+ }
184
+ /**
185
+ * Get scalar fields for a related table to include in relation queries
186
+ * Uses only the _meta query data - no hardcoded field names or assumptions
187
+ */
188
+ function getRelatedTableScalarFields(relationField, table, allTables) {
189
+ // Find the related table name
190
+ let referencedTableName;
191
+ // Check belongsTo relations
192
+ const belongsToRel = table.relations.belongsTo.find((rel) => rel.fieldName === relationField);
193
+ if (belongsToRel) {
194
+ referencedTableName = belongsToRel.referencesTable;
195
+ }
196
+ // Check hasOne relations
197
+ if (!referencedTableName) {
198
+ const hasOneRel = table.relations.hasOne.find((rel) => rel.fieldName === relationField);
199
+ if (hasOneRel) {
200
+ referencedTableName = hasOneRel.referencedByTable;
201
+ }
202
+ }
203
+ // Check hasMany relations
204
+ if (!referencedTableName) {
205
+ const hasManyRel = table.relations.hasMany.find((rel) => rel.fieldName === relationField);
206
+ if (hasManyRel) {
207
+ referencedTableName = hasManyRel.referencedByTable;
208
+ }
209
+ }
210
+ // Check manyToMany relations
211
+ if (!referencedTableName) {
212
+ const manyToManyRel = table.relations.manyToMany.find((rel) => rel.fieldName === relationField);
213
+ if (manyToManyRel) {
214
+ referencedTableName = manyToManyRel.rightTable;
215
+ }
216
+ }
217
+ if (!referencedTableName) {
218
+ // No related table found - return empty selection
219
+ return {};
220
+ }
221
+ // Find the related table in allTables
222
+ const relatedTable = allTables.find((t) => t.name === referencedTableName);
223
+ if (!relatedTable) {
224
+ // Related table not found in schema - return empty selection
225
+ return {};
226
+ }
227
+ // Get ALL scalar fields from the related table (non-relational fields)
228
+ // This is completely dynamic based on the actual schema
229
+ const scalarFields = relatedTable.fields
230
+ .filter((field) => !isRelationalField(field.name, relatedTable))
231
+ .map((field) => field.name);
232
+ // Perf guardrail: select a small, display-oriented subset.
233
+ const MAX_RELATED_FIELDS = 8;
234
+ const preferred = [
235
+ 'displayName',
236
+ 'fullName',
237
+ 'preferredName',
238
+ 'nickname',
239
+ 'firstName',
240
+ 'lastName',
241
+ 'username',
242
+ 'email',
243
+ 'name',
244
+ 'title',
245
+ 'label',
246
+ 'slug',
247
+ 'code',
248
+ 'createdAt',
249
+ 'updatedAt',
250
+ ];
251
+ const scalarFieldSet = new Set(scalarFields);
252
+ // Use Set for O(1) duplicate checking
253
+ const includedSet = new Set();
254
+ const included = [];
255
+ const push = (fieldName) => {
256
+ if (!fieldName)
257
+ return;
258
+ if (!scalarFieldSet.has(fieldName))
259
+ return;
260
+ if (includedSet.has(fieldName))
261
+ return;
262
+ if (included.length >= MAX_RELATED_FIELDS)
263
+ return;
264
+ includedSet.add(fieldName);
265
+ included.push(fieldName);
266
+ };
267
+ // Always try to include stable identifiers first.
268
+ push('id');
269
+ push('rowId');
270
+ push('nodeId');
271
+ for (const fieldName of preferred)
272
+ push(fieldName);
273
+ for (const fieldName of scalarFields)
274
+ push(fieldName);
275
+ const selection = {};
276
+ for (const fieldName of included)
277
+ selection[fieldName] = true;
278
+ return selection;
279
+ }
280
+ /**
281
+ * Get all available relation fields from a table
282
+ */
283
+ export function getAvailableRelations(table) {
284
+ const relations = [];
285
+ // Add belongsTo relations
286
+ table.relations.belongsTo.forEach((rel) => {
287
+ if (rel.fieldName) {
288
+ relations.push({
289
+ fieldName: rel.fieldName,
290
+ type: 'belongsTo',
291
+ referencedTable: rel.referencesTable || undefined,
292
+ });
293
+ }
294
+ });
295
+ // Add hasOne relations
296
+ table.relations.hasOne.forEach((rel) => {
297
+ if (rel.fieldName) {
298
+ relations.push({
299
+ fieldName: rel.fieldName,
300
+ type: 'hasOne',
301
+ referencedTable: rel.referencedByTable || undefined,
302
+ });
303
+ }
304
+ });
305
+ // Add hasMany relations
306
+ table.relations.hasMany.forEach((rel) => {
307
+ if (rel.fieldName) {
308
+ relations.push({
309
+ fieldName: rel.fieldName,
310
+ type: 'hasMany',
311
+ referencedTable: rel.referencedByTable || undefined,
312
+ });
313
+ }
314
+ });
315
+ // Add manyToMany relations
316
+ table.relations.manyToMany.forEach((rel) => {
317
+ if (rel.fieldName) {
318
+ relations.push({
319
+ fieldName: rel.fieldName,
320
+ type: 'manyToMany',
321
+ referencedTable: rel.rightTable || undefined,
322
+ });
323
+ }
324
+ });
325
+ return relations;
326
+ }
327
+ /**
328
+ * Validate field selection against table schema
329
+ */
330
+ export function validateFieldSelection(selection, table) {
331
+ const errors = [];
332
+ if (typeof selection === 'string') {
333
+ // Presets are always valid
334
+ return { isValid: true, errors: [] };
335
+ }
336
+ const tableFieldNames = table.fields.map((f) => f.name);
337
+ // Validate select fields
338
+ if (selection.select) {
339
+ selection.select.forEach((field) => {
340
+ if (!tableFieldNames.includes(field)) {
341
+ errors.push(`Field '${field}' does not exist in table '${table.name}'`);
342
+ }
343
+ });
344
+ }
345
+ // Validate includeRelations fields
346
+ if (selection.includeRelations) {
347
+ selection.includeRelations.forEach((field) => {
348
+ if (!isRelationalField(field, table)) {
349
+ errors.push(`Field '${field}' is not a relational field in table '${table.name}'`);
350
+ }
351
+ });
352
+ }
353
+ // Validate include fields
354
+ if (selection.include) {
355
+ Object.keys(selection.include).forEach((field) => {
356
+ if (!isRelationalField(field, table)) {
357
+ errors.push(`Field '${field}' is not a relational field in table '${table.name}'`);
358
+ }
359
+ });
360
+ }
361
+ // Validate exclude fields
362
+ if (selection.exclude) {
363
+ selection.exclude.forEach((field) => {
364
+ if (!tableFieldNames.includes(field)) {
365
+ errors.push(`Exclude field '${field}' does not exist in table '${table.name}'`);
366
+ }
367
+ });
368
+ }
369
+ // Validate maxDepth
370
+ if (selection.maxDepth !== undefined) {
371
+ if (typeof selection.maxDepth !== 'number' ||
372
+ selection.maxDepth < 0 ||
373
+ selection.maxDepth > 5) {
374
+ errors.push('maxDepth must be a number between 0 and 5');
375
+ }
376
+ }
377
+ return {
378
+ isValid: errors.length === 0,
379
+ errors,
380
+ };
381
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Generators barrel export
3
+ *
4
+ * Re-exports all query/mutation generation functions and naming helpers.
5
+ */
6
+ // SELECT, FindOne, Count query generators
7
+ export { buildSelect, buildFindOne, buildCount, cleanTableToMetaObject, createASTQueryBuilder, generateIntrospectionSchema, toCamelCasePlural, toOrderByTypeName, } from './select';
8
+ // Mutation generators (CREATE, UPDATE, DELETE)
9
+ export { buildPostGraphileCreate, buildPostGraphileUpdate, buildPostGraphileDelete, } from './mutations';
10
+ // Field selection utilities
11
+ export { convertToSelectionOptions, isRelationalField, getAvailableRelations, validateFieldSelection, } from './field-selector';
12
+ // Naming helpers (server-aware inflection)
13
+ export { normalizeInflectionValue, toCamelCaseSingular, toCreateMutationName, toUpdateMutationName, toDeleteMutationName, toCreateInputTypeName, toUpdateInputTypeName, toDeleteInputTypeName, toFilterTypeName, toPatchFieldName, toOrderByEnumValue, } from './naming-helpers';
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Mutation generators for CREATE, UPDATE, and DELETE operations
3
+ * Uses AST-based approach for PostGraphile-compatible mutations
4
+ */
5
+ import * as t from 'gql-ast';
6
+ import { OperationTypeNode, print } from 'graphql';
7
+ import { TypedDocumentString } from '../client/typed-document';
8
+ import { getCustomAstForCleanField, requiresSubfieldSelection, } from '../custom-ast';
9
+ import { isRelationalField } from './field-selector';
10
+ import { toCamelCaseSingular, toCreateInputTypeName, toCreateMutationName, toDeleteInputTypeName, toDeleteMutationName, toUpdateInputTypeName, toUpdateMutationName, } from './naming-helpers';
11
+ /**
12
+ * Generate field selections for PostGraphile mutations using custom AST logic
13
+ * This handles both scalar fields and complex types that require subfield selections
14
+ */
15
+ function generateFieldSelections(table) {
16
+ return table.fields
17
+ .filter((field) => !isRelationalField(field.name, table)) // Exclude relational fields
18
+ .map((field) => {
19
+ if (requiresSubfieldSelection(field)) {
20
+ // Use custom AST generation for complex types
21
+ return getCustomAstForCleanField(field);
22
+ }
23
+ else {
24
+ // Use simple field selection for scalar types
25
+ return t.field({ name: field.name });
26
+ }
27
+ });
28
+ }
29
+ /**
30
+ * Build PostGraphile-style CREATE mutation
31
+ * PostGraphile expects: mutation { createTableName(input: { tableName: TableNameInput! }) { tableName { ... } } }
32
+ */
33
+ export function buildPostGraphileCreate(table, _allTables, _options = {}) {
34
+ const mutationName = toCreateMutationName(table.name, table);
35
+ const singularName = toCamelCaseSingular(table.name, table);
36
+ const inputTypeName = toCreateInputTypeName(table.name, table);
37
+ // Create the variable definition for $input
38
+ const variableDefinitions = [
39
+ t.variableDefinition({
40
+ variable: t.variable({ name: 'input' }),
41
+ type: t.nonNullType({
42
+ type: t.namedType({ type: inputTypeName }),
43
+ }),
44
+ }),
45
+ ];
46
+ // Create the mutation arguments
47
+ const mutationArgs = [
48
+ t.argument({
49
+ name: 'input',
50
+ value: t.variable({ name: 'input' }),
51
+ }),
52
+ ];
53
+ // Get the field selections for the return value using custom AST logic
54
+ const fieldSelections = generateFieldSelections(table);
55
+ // Build the mutation AST
56
+ const ast = t.document({
57
+ definitions: [
58
+ t.operationDefinition({
59
+ operation: OperationTypeNode.MUTATION,
60
+ name: `${mutationName}Mutation`,
61
+ variableDefinitions,
62
+ selectionSet: t.selectionSet({
63
+ selections: [
64
+ t.field({
65
+ name: mutationName,
66
+ args: mutationArgs,
67
+ selectionSet: t.selectionSet({
68
+ selections: [
69
+ t.field({
70
+ name: singularName,
71
+ selectionSet: t.selectionSet({
72
+ selections: fieldSelections,
73
+ }),
74
+ }),
75
+ ],
76
+ }),
77
+ }),
78
+ ],
79
+ }),
80
+ }),
81
+ ],
82
+ });
83
+ // Print the AST to get the query string
84
+ const queryString = print(ast);
85
+ return new TypedDocumentString(queryString, {
86
+ __ast: ast,
87
+ });
88
+ }
89
+ /**
90
+ * Build PostGraphile-style UPDATE mutation
91
+ * PostGraphile expects: mutation { updateTableName(input: { id: UUID!, patch: TableNamePatch! }) { tableName { ... } } }
92
+ */
93
+ export function buildPostGraphileUpdate(table, _allTables, _options = {}) {
94
+ const mutationName = toUpdateMutationName(table.name, table);
95
+ const singularName = toCamelCaseSingular(table.name, table);
96
+ const inputTypeName = toUpdateInputTypeName(table.name);
97
+ // Create the variable definition for $input
98
+ const variableDefinitions = [
99
+ t.variableDefinition({
100
+ variable: t.variable({ name: 'input' }),
101
+ type: t.nonNullType({
102
+ type: t.namedType({ type: inputTypeName }),
103
+ }),
104
+ }),
105
+ ];
106
+ // Create the mutation arguments
107
+ const mutationArgs = [
108
+ t.argument({
109
+ name: 'input',
110
+ value: t.variable({ name: 'input' }),
111
+ }),
112
+ ];
113
+ // Get the field selections for the return value using custom AST logic
114
+ const fieldSelections = generateFieldSelections(table);
115
+ // Build the mutation AST
116
+ const ast = t.document({
117
+ definitions: [
118
+ t.operationDefinition({
119
+ operation: OperationTypeNode.MUTATION,
120
+ name: `${mutationName}Mutation`,
121
+ variableDefinitions,
122
+ selectionSet: t.selectionSet({
123
+ selections: [
124
+ t.field({
125
+ name: mutationName,
126
+ args: mutationArgs,
127
+ selectionSet: t.selectionSet({
128
+ selections: [
129
+ t.field({
130
+ name: singularName,
131
+ selectionSet: t.selectionSet({
132
+ selections: fieldSelections,
133
+ }),
134
+ }),
135
+ ],
136
+ }),
137
+ }),
138
+ ],
139
+ }),
140
+ }),
141
+ ],
142
+ });
143
+ // Print the AST to get the query string
144
+ const queryString = print(ast);
145
+ return new TypedDocumentString(queryString, {
146
+ __ast: ast,
147
+ });
148
+ }
149
+ /**
150
+ * Build PostGraphile-style DELETE mutation
151
+ * PostGraphile expects: mutation { deleteTableName(input: { id: UUID! }) { clientMutationId } }
152
+ */
153
+ export function buildPostGraphileDelete(table, _allTables, _options = {}) {
154
+ const mutationName = toDeleteMutationName(table.name, table);
155
+ const inputTypeName = toDeleteInputTypeName(table.name);
156
+ // Create the variable definition for $input
157
+ const variableDefinitions = [
158
+ t.variableDefinition({
159
+ variable: t.variable({ name: 'input' }),
160
+ type: t.nonNullType({
161
+ type: t.namedType({ type: inputTypeName }),
162
+ }),
163
+ }),
164
+ ];
165
+ // Create the mutation arguments
166
+ const mutationArgs = [
167
+ t.argument({
168
+ name: 'input',
169
+ value: t.variable({ name: 'input' }),
170
+ }),
171
+ ];
172
+ // PostGraphile delete mutations typically return clientMutationId
173
+ const fieldSelections = [t.field({ name: 'clientMutationId' })];
174
+ // Build the mutation AST
175
+ const ast = t.document({
176
+ definitions: [
177
+ t.operationDefinition({
178
+ operation: OperationTypeNode.MUTATION,
179
+ name: `${mutationName}Mutation`,
180
+ variableDefinitions,
181
+ selectionSet: t.selectionSet({
182
+ selections: [
183
+ t.field({
184
+ name: mutationName,
185
+ args: mutationArgs,
186
+ selectionSet: t.selectionSet({
187
+ selections: fieldSelections,
188
+ }),
189
+ }),
190
+ ],
191
+ }),
192
+ }),
193
+ ],
194
+ });
195
+ // Print the AST to get the query string
196
+ const queryString = print(ast);
197
+ return new TypedDocumentString(queryString, {
198
+ __ast: ast,
199
+ });
200
+ }