Skip to content

Commit e9394c9

Browse files
authored
Move custom GraphQL scalar definitions to their own file (#9543)
1 parent 4493026 commit e9394c9

File tree

2 files changed

+238
-237
lines changed

2 files changed

+238
-237
lines changed

packages/core/src/types/schema/graphql-ts-schema.ts

Lines changed: 2 additions & 237 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
import type { ReadStream } from 'node:fs'
2-
import * as graphqlTsSchema from '@graphql-ts/schema'
3-
// @ts-expect-error
4-
import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
1+
import type * as graphqlTsSchema from '@graphql-ts/schema'
52
import type { GraphQLFieldExtensions, GraphQLResolveInfo } from 'graphql'
6-
import { GraphQLError, GraphQLScalarType } from 'graphql'
7-
import { Decimal as DecimalValue } from 'decimal.js'
83
import type { KeystoneContext } from '../context'
9-
import type { JSONValue } from '../utils'
104
import { field as fieldd } from './schema-api-with-context'
115

126
export {
@@ -42,7 +36,7 @@ export { bindGraphQLSchemaAPIToContext } from '@graphql-ts/schema'
4236
export type { BaseSchemaMeta, Extension } from '@graphql-ts/extend'
4337
export { extend, wrap } from '@graphql-ts/extend'
4438
export { fields, interface, interfaceField, object, union } from './schema-api-with-context'
45-
39+
export { BigInt, CalendarDay, DateTime, Decimal, Empty, Hex, JSON, Upload } from './scalars'
4640
// TODO: remove when we use { graphql } from '.keystone'
4741
type SomeTypeThatIsntARecordOfArgs = string
4842
export type FieldFuncResolve<
@@ -122,235 +116,6 @@ type FieldFunc = <
122116
export const field = fieldd as FieldFunc
123117
// TODO: remove when we use { graphql } from '.keystone'
124118

125-
export const JSON = graphqlTsSchema.g.scalar<JSONValue>(
126-
new GraphQLScalarType({
127-
name: 'JSON',
128-
description:
129-
'The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).',
130-
specifiedByURL: 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf',
131-
// the defaults for serialize, parseValue and parseLiteral do what makes sense for JSON
132-
})
133-
)
134-
135-
// avoiding using Buffer.from/etc. because we want a plain Uint8Array and that would be an extra conversion
136-
// when https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toHex and etc.
137-
// is available we should use that instead
138-
139-
function hexToBytes(value: string): Uint8Array {
140-
if (!/^[0-9a-fA-F]*$/.test(value)) {
141-
throw new GraphQLError('Hex values must be a string of hexadecimal characters')
142-
}
143-
if (value.length % 2 !== 0) {
144-
throw new GraphQLError('Hex values must have an even number of characters')
145-
}
146-
const bytes = new Uint8Array(value.length / 2)
147-
for (let i = 0; i < bytes.byteLength; i += 1) {
148-
const start = i * 2
149-
bytes[i] = parseInt(value.slice(start, start + 2), 16)
150-
}
151-
return bytes
152-
}
153-
154-
function bytesToHex(bytes: Uint8Array): string {
155-
let str = ''
156-
for (const byte of bytes) {
157-
str += byte.toString(16).padStart(2, '0')
158-
}
159-
return str
160-
}
161-
162-
export const Hex = graphqlTsSchema.g.scalar<Uint8Array>(
163-
new GraphQLScalarType({
164-
name: 'Hex',
165-
description: 'The `Hex` scalar type represents bytes as a string of hexadecimal characters.',
166-
parseLiteral(value) {
167-
if (value.kind !== 'StringValue') {
168-
throw new GraphQLError('Hex only accepts values as strings')
169-
}
170-
return hexToBytes(value.value)
171-
},
172-
parseValue(value) {
173-
// so that when you're doing a mutation in a resolver, you can just pass in a Uint8Array directly
174-
if (value instanceof Uint8Array) {
175-
// duplicate it though to avoid any weirdness with the array being mutated
176-
// + ensuring that if you pass in a Buffer, resolvers recieve a normal Uint8Array
177-
return Uint8Array.from(value)
178-
}
179-
if (typeof value !== 'string') {
180-
throw new GraphQLError('Hex only accepts values as strings')
181-
}
182-
return hexToBytes(value)
183-
},
184-
serialize(value) {
185-
if (!(value instanceof Uint8Array)) {
186-
throw new GraphQLError(`unexpected value provided to Hex scalar: ${value}`)
187-
}
188-
return bytesToHex(value)
189-
},
190-
})
191-
)
192-
193-
type FileUpload = {
194-
filename: string
195-
mimetype: string
196-
encoding: string
197-
createReadStream(): ReadStream
198-
}
199-
200-
export const Upload = graphqlTsSchema.g.scalar<Promise<FileUpload>>(GraphQLUpload)
201-
202-
// - Decimal.js throws on invalid inputs
203-
// - Decimal.js can represent +Infinity and -Infinity, these aren't values in Postgres' decimal,
204-
// NaN is but Prisma doesn't support it
205-
// .isFinite refers to +Infinity, -Infinity and NaN
206-
export const Decimal = graphqlTsSchema.graphql.scalar<DecimalValue & { scaleToPrint?: number }>(
207-
new GraphQLScalarType({
208-
name: 'Decimal',
209-
serialize(value) {
210-
if (!DecimalValue.isDecimal(value))
211-
throw new GraphQLError(`unexpected value provided to Decimal scalar: ${value}`)
212-
const cast = value as DecimalValue & { scaleToPrint?: number }
213-
if (cast.scaleToPrint !== undefined) return value.toFixed(cast.scaleToPrint)
214-
return value.toString()
215-
},
216-
parseLiteral(value) {
217-
if (value.kind !== 'StringValue')
218-
throw new GraphQLError('Decimal only accepts values as strings')
219-
const decimal = new DecimalValue(value.value)
220-
if (!decimal.isFinite()) throw new GraphQLError('Decimal values must be finite')
221-
return decimal
222-
},
223-
parseValue(value) {
224-
if (DecimalValue.isDecimal(value)) {
225-
if (!value.isFinite()) throw new GraphQLError('Decimal values must be finite')
226-
return value
227-
}
228-
if (typeof value !== 'string')
229-
throw new GraphQLError('Decimal only accepts values as strings')
230-
const decimal = new DecimalValue(value)
231-
if (!decimal.isFinite()) throw new GraphQLError('Decimal values must be finite')
232-
return decimal
233-
},
234-
})
235-
)
236-
237-
export const BigInt = graphqlTsSchema.graphql.scalar<bigint>(
238-
new GraphQLScalarType({
239-
name: 'BigInt',
240-
serialize(value) {
241-
if (typeof value !== 'bigint')
242-
throw new GraphQLError(`unexpected value provided to BigInt scalar: ${value}`)
243-
return value.toString()
244-
},
245-
parseLiteral(value) {
246-
if (value.kind !== 'StringValue')
247-
throw new GraphQLError('BigInt only accepts values as strings')
248-
return globalThis.BigInt(value.value)
249-
},
250-
parseValue(value) {
251-
if (typeof value === 'bigint') return value
252-
if (typeof value !== 'string') throw new GraphQLError('BigInt only accepts values as strings')
253-
return globalThis.BigInt(value)
254-
},
255-
})
256-
)
257-
258-
// from https://github.com/excitement-engineer/graphql-iso-date/blob/master/src/utils/validator.js#L121
259-
// this is also what prisma uses https://github.com/prisma/prisma/blob/20b58fe65d581bcb43c0d5c28d4b89cabc2d99b2/packages/client/src/runtime/utils/common.ts#L126-L128
260-
const RFC_3339_REGEX =
261-
/^(\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60))(\.\d{1,})?(([Z])|([+|-]([01][0-9]|2[0-3]):[0-5][0-9]))$/
262-
263-
function parseDate(input: string): Date {
264-
if (!RFC_3339_REGEX.test(input)) {
265-
throw new GraphQLError(
266-
'DateTime scalars must be in the form of a full ISO 8601 date-time string'
267-
)
268-
}
269-
const parsed = new Date(input)
270-
if (isNaN(parsed.valueOf())) {
271-
throw new GraphQLError(
272-
'DateTime scalars must be in the form of a full ISO 8601 date-time string'
273-
)
274-
}
275-
return parsed
276-
}
277-
278-
export const DateTime = graphqlTsSchema.g.scalar<Date>(
279-
new GraphQLScalarType({
280-
name: 'DateTime',
281-
specifiedByURL: 'https://datatracker.ietf.org/doc/html/rfc3339#section-5.6',
282-
serialize(value: unknown) {
283-
if (!(value instanceof Date) || isNaN(value.valueOf())) {
284-
throw new GraphQLError(`unexpected value provided to DateTime scalar: ${value}`)
285-
}
286-
return value.toISOString()
287-
},
288-
parseLiteral(value) {
289-
if (value.kind !== 'StringValue') {
290-
throw new GraphQLError('DateTime only accepts values as strings')
291-
}
292-
return parseDate(value.value)
293-
},
294-
parseValue(value: unknown) {
295-
if (value instanceof Date) return value
296-
if (typeof value !== 'string') {
297-
throw new GraphQLError('DateTime only accepts values as strings')
298-
}
299-
return parseDate(value)
300-
},
301-
})
302-
)
303-
304-
const RFC_3339_FULL_DATE_REGEX = /^\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/
305-
306-
function validateCalendarDay(input: string) {
307-
if (!RFC_3339_FULL_DATE_REGEX.test(input)) {
308-
throw new GraphQLError('CalendarDay scalars must be in the form of a full-date ISO 8601 string')
309-
}
310-
}
311-
312-
export const CalendarDay = graphqlTsSchema.g.scalar<string>(
313-
new GraphQLScalarType({
314-
name: 'CalendarDay',
315-
specifiedByURL: 'https://datatracker.ietf.org/doc/html/rfc3339#section-5.6',
316-
serialize(value: unknown) {
317-
if (typeof value !== 'string') {
318-
throw new GraphQLError(`unexpected value provided to CalendarDay scalar: ${value}`)
319-
}
320-
return value
321-
},
322-
parseLiteral(value) {
323-
if (value.kind !== 'StringValue') {
324-
throw new GraphQLError('CalendarDay only accepts values as strings')
325-
}
326-
validateCalendarDay(value.value)
327-
return value.value
328-
},
329-
parseValue(value: unknown) {
330-
if (typeof value !== 'string') {
331-
throw new GraphQLError('CalendarDay only accepts values as strings')
332-
}
333-
validateCalendarDay(value)
334-
return value
335-
},
336-
})
337-
)
338-
339-
export const Empty = graphqlTsSchema.g.scalar<{}>(
340-
new GraphQLScalarType({
341-
name: 'Empty',
342-
serialize(value) {
343-
return null
344-
},
345-
parseLiteral(value) {
346-
return {}
347-
},
348-
parseValue(value) {
349-
return {}
350-
},
351-
})
352-
)
353-
354119
export type NullableType<Context extends KeystoneContext = KeystoneContext> =
355120
graphqlTsSchema.NullableType<Context>
356121
export type Type<Context extends KeystoneContext = KeystoneContext> = graphqlTsSchema.Type<Context>

0 commit comments

Comments
 (0)