Skip to content

Commit 29d9327

Browse files
yaacovCRardatanJoviDeCroockHkmuxonx4l
committed
add bigint support
Co-authored-by: Arda TANRIKULU <ardatanrikulu@gmail.com> Co-authored-by: jdecroock <decroockjovi@gmail.com> Co-authored-by: Hkmu <3169251+hkmu@users.noreply.github.com> Co-authored-by: xonx <119700621+xonx4l@users.noreply.github.com>
1 parent 2d309c6 commit 29d9327

File tree

7 files changed

+171
-2
lines changed

7 files changed

+171
-2
lines changed

src/type/__tests__/scalars-test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,22 @@ describe('Type System: Specified scalar types', () => {
2121
expect(coerceInputValue(1)).to.equal(1);
2222
expect(coerceInputValue(0)).to.equal(0);
2323
expect(coerceInputValue(-1)).to.equal(-1);
24+
expect(coerceInputValue(1n)).to.equal(1);
25+
expect(coerceInputValue(0n)).to.equal(0);
26+
expect(coerceInputValue(-1n)).to.equal(-1);
2427

2528
expect(() => coerceInputValue(9876504321)).to.throw(
2629
'Int cannot represent non 32-bit signed integer value: 9876504321',
2730
);
2831
expect(() => coerceInputValue(-9876504321)).to.throw(
2932
'Int cannot represent non 32-bit signed integer value: -9876504321',
3033
);
34+
expect(() => coerceInputValue(2147483648n)).to.throw(
35+
'Int cannot represent non 32-bit signed integer value: 2147483648',
36+
);
37+
expect(() => coerceInputValue(-2147483649n)).to.throw(
38+
'Int cannot represent non 32-bit signed integer value: -2147483649',
39+
);
3140
expect(() => coerceInputValue(0.1)).to.throw(
3241
'Int cannot represent non-integer value: 0.1',
3342
);
@@ -119,6 +128,9 @@ describe('Type System: Specified scalar types', () => {
119128
expect(coerceOutputValue(1e5)).to.equal(100000);
120129
expect(coerceOutputValue(false)).to.equal(0);
121130
expect(coerceOutputValue(true)).to.equal(1);
131+
expect(coerceOutputValue(1n)).to.equal(1);
132+
expect(coerceOutputValue(0n)).to.equal(0);
133+
expect(coerceOutputValue(-1n)).to.equal(-1);
122134

123135
const customValueOfObj = {
124136
value: 5,
@@ -157,6 +169,12 @@ describe('Type System: Specified scalar types', () => {
157169
expect(() => coerceOutputValue('-9876504321')).to.throw(
158170
'Int cannot represent non 32-bit signed integer value: "-9876504321"',
159171
);
172+
expect(() => coerceOutputValue(2147483648n)).to.throw(
173+
'Int cannot represent non 32-bit signed integer value: 2147483648',
174+
);
175+
expect(() => coerceOutputValue(-2147483649n)).to.throw(
176+
'Int cannot represent non 32-bit signed integer value: -2147483649',
177+
);
160178

161179
// Too big to represent as an Int in JavaScript or GraphQL
162180
expect(() => coerceOutputValue(1e100)).to.throw(
@@ -196,13 +214,23 @@ describe('Type System: Specified scalar types', () => {
196214
expect(coerceInputValue(-1)).to.equal(-1);
197215
expect(coerceInputValue(0.1)).to.equal(0.1);
198216
expect(coerceInputValue(Math.PI)).to.equal(Math.PI);
217+
expect(coerceInputValue(1n)).to.equal(1);
218+
expect(coerceInputValue(0n)).to.equal(0);
219+
expect(coerceInputValue(-1n)).to.equal(-1);
220+
expect(coerceInputValue(9007199254740992n)).to.equal(9007199254740992);
199221

200222
expect(() => coerceInputValue(NaN)).to.throw(
201223
'Float cannot represent non numeric value: NaN',
202224
);
203225
expect(() => coerceInputValue(Infinity)).to.throw(
204226
'Float cannot represent non numeric value: Infinity',
205227
);
228+
expect(() => coerceInputValue(9007199254740993n)).to.throw(
229+
'Float cannot represent non numeric value: 9007199254740993 (value would lose precision)',
230+
);
231+
expect(() => coerceInputValue(2n ** 1024n)).to.throw(
232+
'Float cannot represent non numeric value: 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216 (value is too large)',
233+
);
206234

207235
expect(() => coerceInputValue(undefined)).to.throw(
208236
'Float cannot represent non numeric value: undefined',
@@ -286,6 +314,10 @@ describe('Type System: Specified scalar types', () => {
286314
expect(coerceOutputValue('-1.1')).to.equal(-1.1);
287315
expect(coerceOutputValue(false)).to.equal(0.0);
288316
expect(coerceOutputValue(true)).to.equal(1.0);
317+
expect(coerceOutputValue(1n)).to.equal(1.0);
318+
expect(coerceOutputValue(0n)).to.equal(0.0);
319+
expect(coerceOutputValue(-1n)).to.equal(-1.0);
320+
expect(coerceOutputValue(9007199254740992n)).to.equal(9007199254740992);
289321

290322
const customValueOfObj = {
291323
value: 5.5,
@@ -301,6 +333,12 @@ describe('Type System: Specified scalar types', () => {
301333
expect(() => coerceOutputValue(Infinity)).to.throw(
302334
'Float cannot represent non numeric value: Infinity',
303335
);
336+
expect(() => coerceOutputValue(9007199254740993n)).to.throw(
337+
'Float cannot represent non numeric value: 9007199254740993 (value would lose precision)',
338+
);
339+
expect(() => coerceOutputValue(2n ** 1024n)).to.throw(
340+
'Float cannot represent non numeric value: 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216 (value is too large)',
341+
);
304342
expect(() => coerceOutputValue('one')).to.throw(
305343
'Float cannot represent non numeric value: "one"',
306344
);
@@ -386,6 +424,7 @@ describe('Type System: Specified scalar types', () => {
386424
expect(coerceOutputValue(-1.1)).to.equal('-1.1');
387425
expect(coerceOutputValue(true)).to.equal('true');
388426
expect(coerceOutputValue(false)).to.equal('false');
427+
expect(coerceOutputValue(123n)).to.equal('123');
389428

390429
const valueOf = () => 'valueOf string';
391430
const toJSON = () => 'toJSON string';
@@ -503,6 +542,8 @@ describe('Type System: Specified scalar types', () => {
503542
expect(coerceOutputValue(0)).to.equal(false);
504543
expect(coerceOutputValue(true)).to.equal(true);
505544
expect(coerceOutputValue(false)).to.equal(false);
545+
expect(coerceOutputValue(1n)).to.equal(true);
546+
expect(coerceOutputValue(0n)).to.equal(false);
506547
expect(
507548
coerceOutputValue({
508549
value: true,
@@ -542,6 +583,9 @@ describe('Type System: Specified scalar types', () => {
542583
expect(coerceInputValue(1)).to.equal('1');
543584
expect(coerceInputValue(0)).to.equal('0');
544585
expect(coerceInputValue(-1)).to.equal('-1');
586+
expect(coerceInputValue(1n)).to.equal('1');
587+
expect(coerceInputValue(0n)).to.equal('0');
588+
expect(coerceInputValue(-1n)).to.equal('-1');
545589

546590
// Maximum and minimum safe numbers in JS
547591
expect(coerceInputValue(9007199254740991)).to.equal('9007199254740991');
@@ -626,6 +670,9 @@ describe('Type System: Specified scalar types', () => {
626670
expect(coerceOutputValue(123)).to.equal('123');
627671
expect(coerceOutputValue(0)).to.equal('0');
628672
expect(coerceOutputValue(-1)).to.equal('-1');
673+
expect(coerceOutputValue(123n)).to.equal('123');
674+
expect(coerceOutputValue(0n)).to.equal('0');
675+
expect(coerceOutputValue(-1n)).to.equal('-1');
629676

630677
const valueOf = () => 'valueOf ID';
631678
const toJSON = () => 'toJSON ID';

src/type/scalars.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export const GraphQLInt = new GraphQLScalarType<number>({
4040
if (typeof coercedValue === 'string') {
4141
return coerceIntFromString(coercedValue);
4242
}
43+
if (typeof coercedValue === 'bigint') {
44+
return coerceIntFromBigInt(coercedValue);
45+
}
4346
throw new GraphQLError(
4447
`Int cannot represent non-integer value: ${inspect(coercedValue)}`,
4548
);
@@ -49,6 +52,9 @@ export const GraphQLInt = new GraphQLScalarType<number>({
4952
if (typeof inputValue === 'number') {
5053
return coerceIntFromNumber(inputValue);
5154
}
55+
if (typeof inputValue === 'bigint') {
56+
return coerceIntFromBigInt(inputValue);
57+
}
5258
throw new GraphQLError(
5359
`Int cannot represent non-integer value: ${inspect(inputValue)}`,
5460
);
@@ -72,8 +78,8 @@ export const GraphQLInt = new GraphQLScalarType<number>({
7278
},
7379
valueToLiteral(value) {
7480
if (
75-
typeof value === 'number' &&
76-
Number.isInteger(value) &&
81+
((typeof value === 'number' && Number.isInteger(value)) ||
82+
typeof value === 'bigint') &&
7783
value <= GRAPHQL_MAX_INT &&
7884
value >= GRAPHQL_MIN_INT
7985
) {
@@ -99,6 +105,9 @@ export const GraphQLFloat = new GraphQLScalarType<number>({
99105
if (typeof coercedValue === 'string') {
100106
return coerceFloatFromString(coercedValue);
101107
}
108+
if (typeof coercedValue === 'bigint') {
109+
return coerceFloatFromBigInt(coercedValue);
110+
}
102111
throw new GraphQLError(
103112
`Float cannot represent non numeric value: ${inspect(coercedValue)}`,
104113
);
@@ -108,6 +117,9 @@ export const GraphQLFloat = new GraphQLScalarType<number>({
108117
if (typeof inputValue === 'number') {
109118
return coerceFloatFromNumber(inputValue);
110119
}
120+
if (typeof inputValue === 'bigint') {
121+
return coerceFloatFromBigInt(inputValue);
122+
}
111123
throw new GraphQLError(
112124
`Float cannot represent non numeric value: ${inspect(inputValue)}`,
113125
);
@@ -149,6 +161,9 @@ export const GraphQLString = new GraphQLScalarType<string>({
149161
if (typeof coercedValue === 'number') {
150162
return coerceStringFromNumber(coercedValue);
151163
}
164+
if (typeof coercedValue === 'bigint') {
165+
return String(coercedValue);
166+
}
152167
throw new GraphQLError(
153168
`String cannot represent value: ${inspect(outputValue)}`,
154169
);
@@ -193,6 +208,9 @@ export const GraphQLBoolean = new GraphQLScalarType<boolean>({
193208
if (typeof coercedValue === 'number') {
194209
return coerceBooleanFromNumber(coercedValue);
195210
}
211+
if (typeof coercedValue === 'bigint') {
212+
return coercedValue !== 0n;
213+
}
196214
throw new GraphQLError(
197215
`Boolean cannot represent a non boolean value: ${inspect(coercedValue)}`,
198216
);
@@ -238,6 +256,9 @@ export const GraphQLID = new GraphQLScalarType<string>({
238256
if (typeof coercedValue === 'number') {
239257
return coerceIDFromNumber(coercedValue);
240258
}
259+
if (typeof coercedValue === 'bigint') {
260+
return String(coercedValue);
261+
}
241262
throw new GraphQLError(
242263
`ID cannot represent value: ${inspect(outputValue)}`,
243264
);
@@ -250,6 +271,9 @@ export const GraphQLID = new GraphQLScalarType<string>({
250271
if (typeof inputValue === 'number') {
251272
return coerceIDFromNumber(inputValue);
252273
}
274+
if (typeof inputValue === 'bigint') {
275+
return String(inputValue);
276+
}
253277
throw new GraphQLError(`ID cannot represent value: ${inspect(inputValue)}`);
254278
},
255279

@@ -274,6 +298,9 @@ export const GraphQLID = new GraphQLScalarType<string>({
274298
if (typeof value === 'number') {
275299
return { kind: Kind.INT, value: coerceIDFromNumber(value) };
276300
}
301+
if (typeof value === 'bigint') {
302+
return { kind: Kind.INT, value: String(value) };
303+
}
277304
},
278305
});
279306

@@ -342,6 +369,15 @@ function coerceIntFromString(value: string): number {
342369
return num;
343370
}
344371

372+
function coerceIntFromBigInt(value: bigint): number {
373+
if (value > GRAPHQL_MAX_INT || value < GRAPHQL_MIN_INT) {
374+
throw new GraphQLError(
375+
`Int cannot represent non 32-bit signed integer value: ${String(value)}`,
376+
);
377+
}
378+
return Number(value);
379+
}
380+
345381
function coerceFloatFromNumber(value: number): number {
346382
if (!Number.isFinite(value)) {
347383
throw new GraphQLError(
@@ -366,6 +402,21 @@ function coerceFloatFromString(value: string): number {
366402
return num;
367403
}
368404

405+
function coerceFloatFromBigInt(coercedValue: bigint): number {
406+
const num = Number(coercedValue);
407+
if (!Number.isFinite(num)) {
408+
throw new GraphQLError(
409+
`Float cannot represent non numeric value: ${inspect(coercedValue)} (value is too large)`,
410+
);
411+
}
412+
if (BigInt(num) !== coercedValue) {
413+
throw new GraphQLError(
414+
`Float cannot represent non numeric value: ${inspect(coercedValue)} (value would lose precision)`,
415+
);
416+
}
417+
return num;
418+
}
419+
369420
function coerceStringFromNumber(value: number): string {
370421
if (!Number.isFinite(value)) {
371422
throw new GraphQLError(`String cannot represent value: ${inspect(value)}`);

src/utilities/__tests__/astFromValue-test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ describe('astFromValue', () => {
4646
kind: 'BooleanValue',
4747
value: true,
4848
});
49+
expect(astFromValue(0n, GraphQLBoolean)).to.deep.equal({
50+
kind: 'BooleanValue',
51+
value: false,
52+
});
53+
expect(astFromValue(1n, GraphQLBoolean)).to.deep.equal({
54+
kind: 'BooleanValue',
55+
value: true,
56+
});
4957

5058
const NonNullBoolean = new GraphQLNonNull(GraphQLBoolean);
5159
expect(astFromValue(0, NonNullBoolean)).to.deep.equal({
@@ -59,6 +67,10 @@ describe('astFromValue', () => {
5967
kind: 'IntValue',
6068
value: '-1',
6169
});
70+
expect(astFromValue(-1n, GraphQLInt)).to.deep.equal({
71+
kind: 'IntValue',
72+
value: '-1',
73+
});
6274

6375
expect(astFromValue(123.0, GraphQLInt)).to.deep.equal({
6476
kind: 'IntValue',
@@ -80,6 +92,9 @@ describe('astFromValue', () => {
8092
expect(() => astFromValue(1e40, GraphQLInt)).to.throw(
8193
'Int cannot represent non 32-bit signed integer value: 1e+40',
8294
);
95+
expect(() => astFromValue(2147483648n, GraphQLInt)).to.throw(
96+
'Int cannot represent non 32-bit signed integer value: 2147483648',
97+
);
8398

8499
expect(() => astFromValue(NaN, GraphQLInt)).to.throw(
85100
'Int cannot represent non-integer value: NaN',
@@ -91,6 +106,10 @@ describe('astFromValue', () => {
91106
kind: 'IntValue',
92107
value: '-1',
93108
});
109+
expect(astFromValue(-1n, GraphQLFloat)).to.deep.equal({
110+
kind: 'IntValue',
111+
value: '-1',
112+
});
94113

95114
expect(astFromValue(123.0, GraphQLFloat)).to.deep.equal({
96115
kind: 'IntValue',
@@ -111,6 +130,16 @@ describe('astFromValue', () => {
111130
kind: 'FloatValue',
112131
value: '1e+40',
113132
});
133+
expect(astFromValue(9007199254740992n, GraphQLFloat)).to.deep.equal({
134+
kind: 'IntValue',
135+
value: '9007199254740992',
136+
});
137+
expect(() => astFromValue(9007199254740993n, GraphQLFloat)).to.throw(
138+
'Float cannot represent non numeric value: 9007199254740993 (value would lose precision)',
139+
);
140+
expect(() => astFromValue(2n ** 1024n, GraphQLFloat)).to.throw(
141+
'Float cannot represent non numeric value: 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216 (value is too large)',
142+
);
114143
});
115144

116145
it('converts String values to String ASTs', () => {
@@ -133,6 +162,10 @@ describe('astFromValue', () => {
133162
kind: 'StringValue',
134163
value: '123',
135164
});
165+
expect(astFromValue(123n, GraphQLString)).to.deep.equal({
166+
kind: 'StringValue',
167+
value: '123',
168+
});
136169

137170
expect(astFromValue(false, GraphQLString)).to.deep.equal({
138171
kind: 'StringValue',
@@ -173,6 +206,10 @@ describe('astFromValue', () => {
173206
kind: 'IntValue',
174207
value: '123',
175208
});
209+
expect(astFromValue(123n, GraphQLID)).to.deep.equal({
210+
kind: 'IntValue',
211+
value: '123',
212+
});
176213

177214
expect(astFromValue('123', GraphQLID)).to.deep.equal({
178215
kind: 'IntValue',
@@ -205,6 +242,10 @@ describe('astFromValue', () => {
205242
kind: 'StringValue',
206243
value: 'value',
207244
});
245+
expect(astFromValue(123n, passthroughScalar)).to.deep.equal({
246+
kind: 'IntValue',
247+
value: '123',
248+
});
208249

209250
expect(() => astFromValue(NaN, passthroughScalar)).to.throw(
210251
'Cannot convert value to AST: NaN.',

src/utilities/__tests__/coerceInputValue-test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ describe('coerceInputValue', () => {
121121
});
122122
});
123123

124+
it('converts BigInt values for numeric scalars', () => {
125+
test(1n, GraphQLInt, 1);
126+
test(1n, GraphQLFloat, 1);
127+
test(1n, GraphQLID, '1');
128+
test(9007199254740993n, GraphQLFloat, undefined);
129+
});
130+
124131
describe('for GraphQLInputObject', () => {
125132
const TestInputObject: GraphQLInputObjectType = new GraphQLInputObjectType({
126133
name: 'TestInputObject',

0 commit comments

Comments
 (0)