@constructive-io/graphql-query 3.20.1 → 3.21.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.
@@ -252,8 +252,9 @@ function convertFieldSelectionToSelectionOptions(table, allTables, options) {
252
252
  */
253
253
  function generateSelectQueryAST(table, allTables, selection, options, relationFieldMap) {
254
254
  const pluralName = toCamelCasePlural(table.name, table);
255
- // Generate field selections
256
- const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap);
255
+ // Generate field selections, collecting any variable definitions from field args
256
+ const fieldArgVarDefs = [];
257
+ const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap, fieldArgVarDefs);
257
258
  // Build the query AST
258
259
  const variableDefinitions = [];
259
260
  const queryArgs = [];
@@ -358,7 +359,7 @@ function generateSelectQueryAST(table, allTables, selection, options, relationFi
358
359
  t.operationDefinition({
359
360
  operation: OperationTypeNode.QUERY,
360
361
  name: `${pluralName}Query`,
361
- variableDefinitions,
362
+ variableDefinitions: [...variableDefinitions, ...fieldArgVarDefs],
362
363
  selectionSet: t.selectionSet({
363
364
  selections: [
364
365
  t.field({
@@ -375,10 +376,23 @@ function generateSelectQueryAST(table, allTables, selection, options, relationFi
375
376
  });
376
377
  return print(ast);
377
378
  }
379
+ /**
380
+ * Convert a TypeRef to a GraphQL AST type node for variable definitions
381
+ */
382
+ function typeRefToGqlAstType(ref) {
383
+ if (ref.kind === 'NON_NULL' && ref.ofType) {
384
+ const inner = typeRefToGqlAstType(ref.ofType);
385
+ return t.nonNullType({ type: inner });
386
+ }
387
+ if (ref.kind === 'LIST' && ref.ofType) {
388
+ return t.listType({ type: typeRefToGqlAstType(ref.ofType) });
389
+ }
390
+ return t.namedType({ type: ref.name ?? 'String' });
391
+ }
378
392
  /**
379
393
  * Generate field selections from SelectionOptions
380
394
  */
381
- function generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap) {
395
+ function generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap, collectedVarDefs) {
382
396
  const DEFAULT_NESTED_RELATION_FIRST = 20;
383
397
  if (!selection) {
384
398
  // Default to all non-relational fields (includes complex fields like JSON, geometry, etc.)
@@ -413,6 +427,34 @@ function generateFieldSelectionsFromOptions(table, allTables, selection, relatio
413
427
  fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias));
414
428
  }
415
429
  }
430
+ else if (typeof fieldOptions === 'object' && fieldOptions.args) {
431
+ // Field with arguments (e.g. requestUploadUrl on bucket types)
432
+ const fieldDef = table.fields.find((f) => f.name === fieldName);
433
+ const fieldArgDefs = fieldDef?.args ?? [];
434
+ const fieldArgNodes = [];
435
+ for (const [argName, _argValue] of Object.entries(fieldOptions.args)) {
436
+ const varName = `${fieldName}_${argName}`;
437
+ const argDef = fieldArgDefs.find((a) => a.name === argName);
438
+ const gqlType = argDef
439
+ ? typeRefToGqlAstType(argDef.type)
440
+ : t.namedType({ type: 'String' });
441
+ collectedVarDefs?.push(t.variableDefinition({
442
+ variable: t.variable({ name: varName }),
443
+ type: gqlType,
444
+ }));
445
+ fieldArgNodes.push(t.argument({
446
+ name: argName,
447
+ value: t.variable({ name: varName }),
448
+ }));
449
+ }
450
+ const nestedSelectObj = fieldOptions.select;
451
+ const nestedFields = Object.entries(nestedSelectObj)
452
+ .filter(([, include]) => include)
453
+ .map(([nestedField]) => t.field({ name: nestedField }));
454
+ fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias, fieldArgNodes, nestedFields.length > 0
455
+ ? t.selectionSet({ selections: nestedFields })
456
+ : undefined));
457
+ }
416
458
  else if (typeof fieldOptions === 'object' && fieldOptions.select) {
417
459
  // Nested field selection (for relation fields)
418
460
  const nestedSelections = [];
@@ -260,12 +260,14 @@ function extractEntityFields(entityType, typeMap, entityToConnection, commentsEn
260
260
  }
261
261
  // Include scalar, enum, and other non-relation fields
262
262
  const fieldDescription = commentsEnabled ? stripSmartComments(field.description) : undefined;
263
+ const fieldArgs = extractFieldArguments(field);
263
264
  fields.push({
264
265
  name: field.name,
265
266
  ...(fieldDescription ? { description: fieldDescription } : {}),
266
267
  type: convertToCleanFieldType(field.type),
267
268
  isNotNull: fieldIsNotNull,
268
269
  hasDefault: fieldHasDefault,
270
+ ...(fieldArgs.length > 0 ? { args: fieldArgs } : {}),
269
271
  });
270
272
  }
271
273
  return fields;
@@ -312,6 +314,43 @@ function convertToCleanFieldType(typeRef) {
312
314
  // They were optional anyway and not used by generators
313
315
  };
314
316
  }
317
+ /**
318
+ * Convert an IntrospectionTypeRef to a clean TypeRef
319
+ */
320
+ function introspectionTypeRefToTypeRef(typeRef) {
321
+ if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
322
+ return {
323
+ kind: 'NON_NULL',
324
+ name: null,
325
+ ofType: introspectionTypeRefToTypeRef(typeRef.ofType),
326
+ };
327
+ }
328
+ if (typeRef.kind === 'LIST' && typeRef.ofType) {
329
+ return {
330
+ kind: 'LIST',
331
+ name: null,
332
+ ofType: introspectionTypeRefToTypeRef(typeRef.ofType),
333
+ };
334
+ }
335
+ return {
336
+ kind: typeRef.kind,
337
+ name: typeRef.name ?? null,
338
+ };
339
+ }
340
+ /**
341
+ * Extract arguments from a field that has them (computed fields with args)
342
+ */
343
+ function extractFieldArguments(field) {
344
+ if (!field.args || field.args.length === 0)
345
+ return [];
346
+ return field.args.map((arg) => ({
347
+ name: arg.name,
348
+ type: introspectionTypeRefToTypeRef(arg.type),
349
+ isRequired: isNonNull(arg.type),
350
+ ...(arg.description ? { description: arg.description } : {}),
351
+ ...(arg.defaultValue != null ? { defaultValue: arg.defaultValue } : {}),
352
+ }));
353
+ }
315
354
  // ============================================================================
316
355
  // Relation Inference
317
356
  // ============================================================================
@@ -293,8 +293,9 @@ function convertFieldSelectionToSelectionOptions(table, allTables, options) {
293
293
  */
294
294
  function generateSelectQueryAST(table, allTables, selection, options, relationFieldMap) {
295
295
  const pluralName = (0, naming_helpers_1.toCamelCasePlural)(table.name, table);
296
- // Generate field selections
297
- const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap);
296
+ // Generate field selections, collecting any variable definitions from field args
297
+ const fieldArgVarDefs = [];
298
+ const fieldSelections = generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap, fieldArgVarDefs);
298
299
  // Build the query AST
299
300
  const variableDefinitions = [];
300
301
  const queryArgs = [];
@@ -399,7 +400,7 @@ function generateSelectQueryAST(table, allTables, selection, options, relationFi
399
400
  t.operationDefinition({
400
401
  operation: graphql_1.OperationTypeNode.QUERY,
401
402
  name: `${pluralName}Query`,
402
- variableDefinitions,
403
+ variableDefinitions: [...variableDefinitions, ...fieldArgVarDefs],
403
404
  selectionSet: t.selectionSet({
404
405
  selections: [
405
406
  t.field({
@@ -416,10 +417,23 @@ function generateSelectQueryAST(table, allTables, selection, options, relationFi
416
417
  });
417
418
  return (0, graphql_1.print)(ast);
418
419
  }
420
+ /**
421
+ * Convert a TypeRef to a GraphQL AST type node for variable definitions
422
+ */
423
+ function typeRefToGqlAstType(ref) {
424
+ if (ref.kind === 'NON_NULL' && ref.ofType) {
425
+ const inner = typeRefToGqlAstType(ref.ofType);
426
+ return t.nonNullType({ type: inner });
427
+ }
428
+ if (ref.kind === 'LIST' && ref.ofType) {
429
+ return t.listType({ type: typeRefToGqlAstType(ref.ofType) });
430
+ }
431
+ return t.namedType({ type: ref.name ?? 'String' });
432
+ }
419
433
  /**
420
434
  * Generate field selections from SelectionOptions
421
435
  */
422
- function generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap) {
436
+ function generateFieldSelectionsFromOptions(table, allTables, selection, relationFieldMap, collectedVarDefs) {
423
437
  const DEFAULT_NESTED_RELATION_FIRST = 20;
424
438
  if (!selection) {
425
439
  // Default to all non-relational fields (includes complex fields like JSON, geometry, etc.)
@@ -454,6 +468,34 @@ function generateFieldSelectionsFromOptions(table, allTables, selection, relatio
454
468
  fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias));
455
469
  }
456
470
  }
471
+ else if (typeof fieldOptions === 'object' && fieldOptions.args) {
472
+ // Field with arguments (e.g. requestUploadUrl on bucket types)
473
+ const fieldDef = table.fields.find((f) => f.name === fieldName);
474
+ const fieldArgDefs = fieldDef?.args ?? [];
475
+ const fieldArgNodes = [];
476
+ for (const [argName, _argValue] of Object.entries(fieldOptions.args)) {
477
+ const varName = `${fieldName}_${argName}`;
478
+ const argDef = fieldArgDefs.find((a) => a.name === argName);
479
+ const gqlType = argDef
480
+ ? typeRefToGqlAstType(argDef.type)
481
+ : t.namedType({ type: 'String' });
482
+ collectedVarDefs?.push(t.variableDefinition({
483
+ variable: t.variable({ name: varName }),
484
+ type: gqlType,
485
+ }));
486
+ fieldArgNodes.push(t.argument({
487
+ name: argName,
488
+ value: t.variable({ name: varName }),
489
+ }));
490
+ }
491
+ const nestedSelectObj = fieldOptions.select;
492
+ const nestedFields = Object.entries(nestedSelectObj)
493
+ .filter(([, include]) => include)
494
+ .map(([nestedField]) => t.field({ name: nestedField }));
495
+ fieldSelections.push(createFieldSelectionNode(resolvedField.name, resolvedField.alias, fieldArgNodes, nestedFields.length > 0
496
+ ? t.selectionSet({ selections: nestedFields })
497
+ : undefined));
498
+ }
457
499
  else if (typeof fieldOptions === 'object' && fieldOptions.select) {
458
500
  // Nested field selection (for relation fields)
459
501
  const nestedSelections = [];
@@ -263,12 +263,14 @@ function extractEntityFields(entityType, typeMap, entityToConnection, commentsEn
263
263
  }
264
264
  // Include scalar, enum, and other non-relation fields
265
265
  const fieldDescription = commentsEnabled ? (0, utils_1.stripSmartComments)(field.description) : undefined;
266
+ const fieldArgs = extractFieldArguments(field);
266
267
  fields.push({
267
268
  name: field.name,
268
269
  ...(fieldDescription ? { description: fieldDescription } : {}),
269
270
  type: convertToCleanFieldType(field.type),
270
271
  isNotNull: fieldIsNotNull,
271
272
  hasDefault: fieldHasDefault,
273
+ ...(fieldArgs.length > 0 ? { args: fieldArgs } : {}),
272
274
  });
273
275
  }
274
276
  return fields;
@@ -315,6 +317,43 @@ function convertToCleanFieldType(typeRef) {
315
317
  // They were optional anyway and not used by generators
316
318
  };
317
319
  }
320
+ /**
321
+ * Convert an IntrospectionTypeRef to a clean TypeRef
322
+ */
323
+ function introspectionTypeRefToTypeRef(typeRef) {
324
+ if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
325
+ return {
326
+ kind: 'NON_NULL',
327
+ name: null,
328
+ ofType: introspectionTypeRefToTypeRef(typeRef.ofType),
329
+ };
330
+ }
331
+ if (typeRef.kind === 'LIST' && typeRef.ofType) {
332
+ return {
333
+ kind: 'LIST',
334
+ name: null,
335
+ ofType: introspectionTypeRefToTypeRef(typeRef.ofType),
336
+ };
337
+ }
338
+ return {
339
+ kind: typeRef.kind,
340
+ name: typeRef.name ?? null,
341
+ };
342
+ }
343
+ /**
344
+ * Extract arguments from a field that has them (computed fields with args)
345
+ */
346
+ function extractFieldArguments(field) {
347
+ if (!field.args || field.args.length === 0)
348
+ return [];
349
+ return field.args.map((arg) => ({
350
+ name: arg.name,
351
+ type: introspectionTypeRefToTypeRef(arg.type),
352
+ isRequired: (0, introspection_1.isNonNull)(arg.type),
353
+ ...(arg.description ? { description: arg.description } : {}),
354
+ ...(arg.defaultValue != null ? { defaultValue: arg.defaultValue } : {}),
355
+ }));
356
+ }
318
357
  // ============================================================================
319
358
  // Relation Inference
320
359
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-query",
3
- "version": "3.20.1",
3
+ "version": "3.21.0",
4
4
  "description": "Constructive GraphQL Query",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "main": "index.js",
@@ -36,7 +36,7 @@
36
36
  "grafast": "1.0.0",
37
37
  "graphile-build-pg": "5.0.0",
38
38
  "graphile-config": "1.0.0",
39
- "graphile-settings": "^4.29.1",
39
+ "graphile-settings": "^4.30.1",
40
40
  "graphql": "16.13.0",
41
41
  "inflection": "^3.0.0",
42
42
  "inflekt": "^0.7.1",
@@ -53,5 +53,5 @@
53
53
  "devDependencies": {
54
54
  "makage": "^0.3.0"
55
55
  },
56
- "gitHead": "44e6712bd8a37e2089418a69d801d67651c89350"
56
+ "gitHead": "97ec8e14f2b0855b0ee0bc732a082e1a91301b64"
57
57
  }
package/types/core.d.ts CHANGED
@@ -78,8 +78,9 @@ export interface QueryFieldSelection {
78
78
  }
79
79
  export interface QuerySelectionOptions {
80
80
  [fieldName: string]: boolean | {
81
- select: Record<string, boolean>;
81
+ select: Record<string, boolean | QuerySelectionOptions>;
82
82
  variables?: GraphQLVariables;
83
+ args?: Record<string, unknown>;
83
84
  };
84
85
  }
85
86
  export interface QueryBuilderInstance {
package/types/schema.d.ts CHANGED
@@ -112,6 +112,22 @@ export interface Field {
112
112
  isNotNull?: boolean | null;
113
113
  /** Whether the column has a DEFAULT value (inferred by comparing entity vs CreateInput field nullability) */
114
114
  hasDefault?: boolean | null;
115
+ /** Arguments for computed fields (e.g. requestUploadUrl on bucket types) */
116
+ args?: FieldArgument[];
117
+ }
118
+ /**
119
+ * Argument on a computed field (not a root operation)
120
+ */
121
+ export interface FieldArgument {
122
+ name: string;
123
+ /** GraphQL type reference */
124
+ type: TypeRef;
125
+ /** Whether this argument is required (NON_NULL) */
126
+ isRequired: boolean;
127
+ /** Description from schema */
128
+ description?: string;
129
+ /** Default value (as string) */
130
+ defaultValue?: string;
115
131
  }
116
132
  /**
117
133
  * Field type information from PostGraphile introspection