diff --git a/src/type/definition.ts b/src/type/definition.ts index d4bc322c1e..6e5f0fe409 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -579,23 +579,27 @@ export interface GraphQLScalarTypeExtensions { * }); * ``` */ -export class GraphQLScalarType { +export class GraphQLScalarType { name: string; description: Maybe; specifiedByURL: Maybe; - serialize: GraphQLScalarSerializer; - parseValue: GraphQLScalarValueParser; - parseLiteral: GraphQLScalarLiteralParser; + serialize: GraphQLScalarSerializer; + parseValue: GraphQLScalarValueParser; + parseLiteral: GraphQLScalarLiteralParser; extensions: Maybe>; astNode: Maybe; extensionASTNodes: ReadonlyArray; - constructor(config: Readonly>) { - const parseValue = config.parseValue ?? identityFunc; + constructor(config: Readonly>) { + const parseValue = + config.parseValue ?? + (identityFunc as GraphQLScalarValueParser); + this.name = config.name; this.description = config.description; this.specifiedByURL = config.specifiedByURL; - this.serialize = config.serialize ?? identityFunc; + this.serialize = + config.serialize ?? (identityFunc as GraphQLScalarSerializer); this.parseValue = parseValue; this.parseLiteral = config.parseLiteral ?? @@ -627,7 +631,7 @@ export class GraphQLScalarType { } } - toConfig(): GraphQLScalarTypeNormalizedConfig { + toConfig(): GraphQLScalarTypeNormalizedConfig { return { name: this.name, description: this.description, @@ -656,16 +660,16 @@ export class GraphQLScalarType { export type GraphQLScalarSerializer = ( outputValue: unknown, -) => Maybe; +) => TExternal; export type GraphQLScalarValueParser = ( inputValue: unknown, -) => Maybe; +) => TInternal; export type GraphQLScalarLiteralParser = ( valueNode: ValueNode, variables?: Maybe>, -) => Maybe; +) => TInternal; export interface GraphQLScalarTypeConfig { name: string; @@ -682,11 +686,11 @@ export interface GraphQLScalarTypeConfig { extensionASTNodes?: Maybe>; } -interface GraphQLScalarTypeNormalizedConfig - extends GraphQLScalarTypeConfig { - serialize: GraphQLScalarSerializer; - parseValue: GraphQLScalarValueParser; - parseLiteral: GraphQLScalarLiteralParser; +interface GraphQLScalarTypeNormalizedConfig + extends GraphQLScalarTypeConfig { + serialize: GraphQLScalarSerializer; + parseValue: GraphQLScalarValueParser; + parseLiteral: GraphQLScalarLiteralParser; extensions: Maybe>; extensionASTNodes: ReadonlyArray; } diff --git a/src/type/scalars.ts b/src/type/scalars.ts index b8a4ee11f4..45b7dbffdf 100644 --- a/src/type/scalars.ts +++ b/src/type/scalars.ts @@ -17,52 +17,51 @@ import { GraphQLScalarType } from './definition'; const MAX_INT = 2147483647; const MIN_INT = -2147483648; -function serializeInt(outputValue: unknown): number { - const coercedValue = serializeObject(outputValue); +export const GraphQLInt = new GraphQLScalarType({ + name: 'Int', + description: + 'The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.', - if (typeof coercedValue === 'boolean') { - return coercedValue ? 1 : 0; - } + serialize(outputValue) { + const coercedValue = serializeObject(outputValue); - let num = coercedValue; - if (typeof coercedValue === 'string' && coercedValue !== '') { - num = Number(coercedValue); - } + if (typeof coercedValue === 'boolean') { + return coercedValue ? 1 : 0; + } - if (typeof num !== 'number' || !Number.isInteger(num)) { - throw new GraphQLError( - `Int cannot represent non-integer value: ${inspect(coercedValue)}`, - ); - } - if (num > MAX_INT || num < MIN_INT) { - throw new GraphQLError( - 'Int cannot represent non 32-bit signed integer value: ' + - inspect(coercedValue), - ); - } - return num; -} + let num = coercedValue; + if (typeof coercedValue === 'string' && coercedValue !== '') { + num = Number(coercedValue); + } -function coerceInt(inputValue: unknown): number { - if (typeof inputValue !== 'number' || !Number.isInteger(inputValue)) { - throw new GraphQLError( - `Int cannot represent non-integer value: ${inspect(inputValue)}`, - ); - } - if (inputValue > MAX_INT || inputValue < MIN_INT) { - throw new GraphQLError( - `Int cannot represent non 32-bit signed integer value: ${inputValue}`, - ); - } - return inputValue; -} + if (typeof num !== 'number' || !Number.isInteger(num)) { + throw new GraphQLError( + `Int cannot represent non-integer value: ${inspect(coercedValue)}`, + ); + } + if (num > MAX_INT || num < MIN_INT) { + throw new GraphQLError( + 'Int cannot represent non 32-bit signed integer value: ' + + inspect(coercedValue), + ); + } + return num; + }, + + parseValue(inputValue) { + if (typeof inputValue !== 'number' || !Number.isInteger(inputValue)) { + throw new GraphQLError( + `Int cannot represent non-integer value: ${inspect(inputValue)}`, + ); + } + if (inputValue > MAX_INT || inputValue < MIN_INT) { + throw new GraphQLError( + `Int cannot represent non 32-bit signed integer value: ${inputValue}`, + ); + } + return inputValue; + }, -export const GraphQLInt: GraphQLScalarType = new GraphQLScalarType({ - name: 'Int', - description: - 'The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.', - serialize: serializeInt, - parseValue: coerceInt, parseLiteral(valueNode) { if (valueNode.kind !== Kind.INT) { throw new GraphQLError( @@ -81,41 +80,40 @@ export const GraphQLInt: GraphQLScalarType = new GraphQLScalarType({ }, }); -function serializeFloat(outputValue: unknown): number { - const coercedValue = serializeObject(outputValue); +export const GraphQLFloat = new GraphQLScalarType({ + name: 'Float', + description: + 'The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).', - if (typeof coercedValue === 'boolean') { - return coercedValue ? 1 : 0; - } + serialize(outputValue) { + const coercedValue = serializeObject(outputValue); - let num = coercedValue; - if (typeof coercedValue === 'string' && coercedValue !== '') { - num = Number(coercedValue); - } + if (typeof coercedValue === 'boolean') { + return coercedValue ? 1 : 0; + } - if (typeof num !== 'number' || !Number.isFinite(num)) { - throw new GraphQLError( - `Float cannot represent non numeric value: ${inspect(coercedValue)}`, - ); - } - return num; -} + let num = coercedValue; + if (typeof coercedValue === 'string' && coercedValue !== '') { + num = Number(coercedValue); + } -function coerceFloat(inputValue: unknown): number { - if (typeof inputValue !== 'number' || !Number.isFinite(inputValue)) { - throw new GraphQLError( - `Float cannot represent non numeric value: ${inspect(inputValue)}`, - ); - } - return inputValue; -} + if (typeof num !== 'number' || !Number.isFinite(num)) { + throw new GraphQLError( + `Float cannot represent non numeric value: ${inspect(coercedValue)}`, + ); + } + return num; + }, + + parseValue(inputValue) { + if (typeof inputValue !== 'number' || !Number.isFinite(inputValue)) { + throw new GraphQLError( + `Float cannot represent non numeric value: ${inspect(inputValue)}`, + ); + } + return inputValue; + }, -export const GraphQLFloat: GraphQLScalarType = new GraphQLScalarType({ - name: 'Float', - description: - 'The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).', - serialize: serializeFloat, - parseValue: coerceFloat, parseLiteral(valueNode) { if (valueNode.kind !== Kind.FLOAT && valueNode.kind !== Kind.INT) { throw new GraphQLError( @@ -127,58 +125,39 @@ export const GraphQLFloat: GraphQLScalarType = new GraphQLScalarType({ }, }); -// Support serializing objects with custom valueOf() or toJSON() functions - -// a common way to represent a complex value which can be represented as -// a string (ex: MongoDB id objects). -function serializeObject(outputValue: unknown): unknown { - if (isObjectLike(outputValue)) { - if (typeof outputValue.valueOf === 'function') { - const valueOfResult = outputValue.valueOf(); - if (!isObjectLike(valueOfResult)) { - return valueOfResult; - } - } - if (typeof outputValue.toJSON === 'function') { - return outputValue.toJSON(); - } - } - return outputValue; -} - -function serializeString(outputValue: unknown): string { - const coercedValue = serializeObject(outputValue); +export const GraphQLString = new GraphQLScalarType({ + name: 'String', + description: + 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', - // Serialize string, boolean and number values to a string, but do not - // attempt to coerce object, function, symbol, or other types as strings. - if (typeof coercedValue === 'string') { - return coercedValue; - } - if (typeof coercedValue === 'boolean') { - return coercedValue ? 'true' : 'false'; - } - if (typeof coercedValue === 'number' && Number.isFinite(coercedValue)) { - return coercedValue.toString(); - } - throw new GraphQLError( - `String cannot represent value: ${inspect(outputValue)}`, - ); -} + serialize(outputValue) { + const coercedValue = serializeObject(outputValue); -function coerceString(inputValue: unknown): string { - if (typeof inputValue !== 'string') { + // Serialize string, boolean and number values to a string, but do not + // attempt to coerce object, function, symbol, or other types as strings. + if (typeof coercedValue === 'string') { + return coercedValue; + } + if (typeof coercedValue === 'boolean') { + return coercedValue ? 'true' : 'false'; + } + if (typeof coercedValue === 'number' && Number.isFinite(coercedValue)) { + return coercedValue.toString(); + } throw new GraphQLError( - `String cannot represent a non string value: ${inspect(inputValue)}`, + `String cannot represent value: ${inspect(outputValue)}`, ); - } - return inputValue; -} + }, + + parseValue(inputValue) { + if (typeof inputValue !== 'string') { + throw new GraphQLError( + `String cannot represent a non string value: ${inspect(inputValue)}`, + ); + } + return inputValue; + }, -export const GraphQLString: GraphQLScalarType = new GraphQLScalarType({ - name: 'String', - description: - 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', - serialize: serializeString, - parseValue: coerceString, parseLiteral(valueNode) { if (valueNode.kind !== Kind.STRING) { throw new GraphQLError( @@ -190,34 +169,33 @@ export const GraphQLString: GraphQLScalarType = new GraphQLScalarType({ }, }); -function serializeBoolean(outputValue: unknown): boolean { - const coercedValue = serializeObject(outputValue); +export const GraphQLBoolean = new GraphQLScalarType({ + name: 'Boolean', + description: 'The `Boolean` scalar type represents `true` or `false`.', - if (typeof coercedValue === 'boolean') { - return coercedValue; - } - if (Number.isFinite(coercedValue)) { - return coercedValue !== 0; - } - throw new GraphQLError( - `Boolean cannot represent a non boolean value: ${inspect(coercedValue)}`, - ); -} + serialize(outputValue) { + const coercedValue = serializeObject(outputValue); -function coerceBoolean(inputValue: unknown): boolean { - if (typeof inputValue !== 'boolean') { + if (typeof coercedValue === 'boolean') { + return coercedValue; + } + if (Number.isFinite(coercedValue)) { + return coercedValue !== 0; + } throw new GraphQLError( - `Boolean cannot represent a non boolean value: ${inspect(inputValue)}`, + `Boolean cannot represent a non boolean value: ${inspect(coercedValue)}`, ); - } - return inputValue; -} + }, + + parseValue(inputValue) { + if (typeof inputValue !== 'boolean') { + throw new GraphQLError( + `Boolean cannot represent a non boolean value: ${inspect(inputValue)}`, + ); + } + return inputValue; + }, -export const GraphQLBoolean: GraphQLScalarType = new GraphQLScalarType({ - name: 'Boolean', - description: 'The `Boolean` scalar type represents `true` or `false`.', - serialize: serializeBoolean, - parseValue: coerceBoolean, parseLiteral(valueNode) { if (valueNode.kind !== Kind.BOOLEAN) { throw new GraphQLError( @@ -229,34 +207,35 @@ export const GraphQLBoolean: GraphQLScalarType = new GraphQLScalarType({ }, }); -function serializeID(outputValue: unknown): string { - const coercedValue = serializeObject(outputValue); - - if (typeof coercedValue === 'string') { - return coercedValue; - } - if (Number.isInteger(coercedValue)) { - return String(coercedValue); - } - throw new GraphQLError(`ID cannot represent value: ${inspect(outputValue)}`); -} - -function coerceID(inputValue: unknown): string { - if (typeof inputValue === 'string') { - return inputValue; - } - if (typeof inputValue === 'number' && Number.isInteger(inputValue)) { - return inputValue.toString(); - } - throw new GraphQLError(`ID cannot represent value: ${inspect(inputValue)}`); -} - -export const GraphQLID: GraphQLScalarType = new GraphQLScalarType({ +export const GraphQLID = new GraphQLScalarType({ name: 'ID', description: 'The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.', - serialize: serializeID, - parseValue: coerceID, + + serialize(outputValue) { + const coercedValue = serializeObject(outputValue); + + if (typeof coercedValue === 'string') { + return coercedValue; + } + if (Number.isInteger(coercedValue)) { + return String(coercedValue); + } + throw new GraphQLError( + `ID cannot represent value: ${inspect(outputValue)}`, + ); + }, + + parseValue(inputValue) { + if (typeof inputValue === 'string') { + return inputValue; + } + if (typeof inputValue === 'number' && Number.isInteger(inputValue)) { + return inputValue.toString(); + } + throw new GraphQLError(`ID cannot represent value: ${inspect(inputValue)}`); + }, + parseLiteral(valueNode) { if (valueNode.kind !== Kind.STRING && valueNode.kind !== Kind.INT) { throw new GraphQLError( @@ -281,3 +260,21 @@ export const specifiedScalarTypes: ReadonlyArray = export function isSpecifiedScalarType(type: GraphQLNamedType): boolean { return specifiedScalarTypes.some(({ name }) => type.name === name); } + +// Support serializing objects with custom valueOf() or toJSON() functions - +// a common way to represent a complex value which can be represented as +// a string (ex: MongoDB id objects). +function serializeObject(outputValue: unknown): unknown { + if (isObjectLike(outputValue)) { + if (typeof outputValue.valueOf === 'function') { + const valueOfResult = outputValue.valueOf(); + if (!isObjectLike(valueOfResult)) { + return valueOfResult; + } + } + if (typeof outputValue.toJSON === 'function') { + return outputValue.toJSON(); + } + } + return outputValue; +}