@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.
- package/esm/generators/select.js +46 -4
- package/esm/introspect/infer-tables.js +39 -0
- package/generators/select.js +46 -4
- package/introspect/infer-tables.js +39 -0
- package/package.json +3 -3
- package/types/core.d.ts +2 -1
- package/types/schema.d.ts +16 -0
package/esm/generators/select.js
CHANGED
|
@@ -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
|
|
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
|
// ============================================================================
|
package/generators/select.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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": "
|
|
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
|