Skip to content

Commit dcdc6eb

Browse files
committed
Support BigInt values
1 parent 778ebec commit dcdc6eb

File tree

4 files changed

+135
-1
lines changed

4 files changed

+135
-1
lines changed

.changeset/wicked-apples-remain.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-tools/utils': patch
3+
---
4+
5+
Support BigInt values

packages/utils/src/astFromValue.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import {
2+
GraphQLInputType,
3+
Kind,
4+
ObjectFieldNode,
5+
ValueNode,
6+
isEnumType,
7+
isInputObjectType,
8+
isLeafType,
9+
isListType,
10+
isNonNullType,
11+
} from 'graphql';
12+
import { isIterableObject, isObjectLike } from './jsutils.js';
13+
import { inspect } from './inspect.js';
14+
import { Maybe } from './types.js';
15+
import { astFromValueUntyped } from './astFromValueUntyped.js';
16+
17+
/**
18+
* Produces a GraphQL Value AST given a JavaScript object.
19+
* Function will match JavaScript/JSON values to GraphQL AST schema format
20+
* by using suggested GraphQLInputType. For example:
21+
*
22+
* astFromValue("value", GraphQLString)
23+
*
24+
* A GraphQL type must be provided, which will be used to interpret different
25+
* JavaScript values.
26+
*
27+
* | JSON Value | GraphQL Value |
28+
* | ------------- | -------------------- |
29+
* | Object | Input Object |
30+
* | Array | List |
31+
* | Boolean | Boolean |
32+
* | String | String / Enum Value |
33+
* | Number | Int / Float |
34+
* | BigInt | Int |
35+
* | Unknown | Enum Value |
36+
* | null | NullValue |
37+
*
38+
*/
39+
export function astFromValue(value: unknown, type: GraphQLInputType): Maybe<ValueNode> {
40+
if (isNonNullType(type)) {
41+
const astValue = astFromValue(value, type.ofType);
42+
if (astValue?.kind === Kind.NULL) {
43+
return null;
44+
}
45+
return astValue;
46+
}
47+
48+
// only explicit null, not undefined, NaN
49+
if (value === null) {
50+
return { kind: Kind.NULL };
51+
}
52+
53+
// undefined
54+
if (value === undefined) {
55+
return null;
56+
}
57+
58+
// Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
59+
// the value is not an array, convert the value using the list's item type.
60+
if (isListType(type)) {
61+
const itemType = type.ofType;
62+
if (isIterableObject(value)) {
63+
const valuesNodes = [];
64+
for (const item of value) {
65+
const itemNode = astFromValue(item, itemType);
66+
if (itemNode != null) {
67+
valuesNodes.push(itemNode);
68+
}
69+
}
70+
return { kind: Kind.LIST, values: valuesNodes };
71+
}
72+
return astFromValue(value, itemType);
73+
}
74+
75+
// Populate the fields of the input object by creating ASTs from each value
76+
// in the JavaScript object according to the fields in the input type.
77+
if (isInputObjectType(type)) {
78+
if (!isObjectLike(value)) {
79+
return null;
80+
}
81+
const fieldNodes: Array<ObjectFieldNode> = [];
82+
for (const field of Object.values(type.getFields())) {
83+
const fieldValue = astFromValue(value[field.name], field.type);
84+
if (fieldValue) {
85+
fieldNodes.push({
86+
kind: Kind.OBJECT_FIELD,
87+
name: { kind: Kind.NAME, value: field.name },
88+
value: fieldValue,
89+
});
90+
}
91+
}
92+
return { kind: Kind.OBJECT, fields: fieldNodes };
93+
}
94+
95+
if (isLeafType(type)) {
96+
// Since value is an internally represented value, it must be serialized
97+
// to an externally represented value before converting into an AST.
98+
const serialized = type.serialize(value);
99+
if (serialized == null) {
100+
return null;
101+
}
102+
103+
if (isEnumType(type)) {
104+
return { kind: Kind.ENUM, value: serialized as string };
105+
}
106+
107+
// ID types can use Int literals.
108+
if (type.name === 'ID' && typeof serialized === 'string' && integerStringRegExp.test(serialized)) {
109+
return { kind: Kind.INT, value: serialized };
110+
}
111+
112+
return astFromValueUntyped(serialized);
113+
}
114+
/* c8 ignore next 3 */
115+
// Not reachable, all possible types have been considered.
116+
console.assert(false, 'Unexpected input type: ' + inspect(type));
117+
}
118+
119+
/**
120+
* IntValue:
121+
* - NegativeSign? 0
122+
* - NegativeSign? NonZeroDigit ( Digit+ )?
123+
*/
124+
const integerStringRegExp = /^-?(?:0|[1-9][0-9]*)$/;

packages/utils/src/astFromValueUntyped.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Kind, ObjectFieldNode, ValueNode } from 'graphql';
1212
* | Boolean | Boolean |
1313
* | String | String |
1414
* | Number | Int / Float |
15+
* | BigInt | Int |
1516
* | null | NullValue |
1617
*
1718
*/
@@ -60,6 +61,10 @@ export function astFromValueUntyped(value: any): ValueNode | null {
6061
return { kind: Kind.BOOLEAN, value };
6162
}
6263

64+
if (typeof value === 'bigint') {
65+
return { kind: Kind.INT, value: String(value) };
66+
}
67+
6368
// JavaScript numbers can be Int or Float values.
6469
if (typeof value === 'number' && isFinite(value)) {
6570
const stringNum = String(value);

packages/utils/src/print-schema-with-directives.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
isSpecifiedDirective,
1515
GraphQLDirective,
1616
DirectiveDefinitionNode,
17-
astFromValue,
1817
ArgumentNode,
1918
SchemaDefinitionNode,
2019
OperationTypeDefinitionNode,
@@ -55,6 +54,7 @@ import { getDirectivesInExtensions } from './get-directives.js';
5554
import { astFromValueUntyped } from './astFromValueUntyped.js';
5655
import { isSome } from './helpers.js';
5756
import { getRootTypeMap } from './rootTypes.js';
57+
import { astFromValue } from './astFromValue.js';
5858

5959
export function getDocumentNodeFromSchema(
6060
schema: GraphQLSchema,

0 commit comments

Comments
 (0)