GraphQL.js の buildSchema でカスタムScalar型を使う
GraphQL.js の buildSchema
でカスタム Scalar 型を利用してみました。
サンプルコードは こちら
はじめに
GraphQL.js では Scalar 型を GraphQLScalarType
のインスタンスとして実装するようになっており、ID
(GraphQLID) や Int
(GraphQLInt) 等の基本的な型が用意されています。(src/type/scalars.ts 参照)
用意されていない型は自前で定義する事ができ、例えば以下のような日付型のカスタム Scalar(名前は Date
とする)は下記のように実装できます。
- GraphQL 上は文字列として表現し、内部的に JavaScript の Date 型を使用
実装例 - カスタム Scalar の Date 型
const GraphQLDate = new GraphQLScalarType<Date, string>({ name: 'Date', // 内部データを GraphQL 上の表現へ変換 serialize: (outputValue) => { if (outputValue instanceof Date) { return outputValue.toISOString() } throw new GraphQLError('non date') }, // GraphQL 上の値を内部データへ変換 parseValue: (inputValue) => { if (typeof inputValue === 'string') { const d = new Date(inputValue) if (isNaN(d.getTime())) { throw new GraphQLError('invalid date') } return d } throw new GraphQLError('non string value') }, // GraphQL 上の表現を内部データへ変換 parseLiteral: (valueNode) => { if (valueNode.kind === Kind.STRING) { const d = new Date(valueNode.value) if (isNaN(d.getTime())) { throw new GraphQLError('invalid date') } return d } throw new GraphQLError('non string value') } })
buildSchema 利用時
GraphQL 上はカスタム Scalar 型を以下のように定義できます。
scalar 型名
これを buildSchema
で処理すると、GraphQLScalarType のデフォルト実装が適用されてしまい、任意の処理へ差し替えたりする事はできないようでした。
そのため、現時点では buildSchema が構築した GraphQLScalarType を後から上書きするような対応が必要となりそうです。
サンプル作成
SampleDate
というカスタム Scalar 型を定義し、それを用いた処理を Deno 用の TypeScript で実装してみました。
buildSchema の結果から SampleDate の型情報を取得して、下記の処理を差し替えています。
serialize
(内部データを GraphQL 上の表現へ変換)parseValue
(GraphQL 上の値を内部データへ変換)parseLiteral
(GraphQL 上の表現を内部データへ変換)
なお、下記で parseLiteral へ variables 引数を付けているのは console.log して内容を確認するためです。
sample.ts
import { graphql, buildSchema, GraphQLError, ValueNode, Kind } from 'https://cdn.skypack.dev/graphql?dts' const schema = buildSchema(` scalar SampleDate type Query { now: SampleDate! nextDay(date: SampleDate!): SampleDate! } `) const toDate = (v: string) => { const d = new Date(v) if (isNaN(d.getTime())) { throw new GraphQLError('invalid date') } return d } type MaybeObjMap = { [key: string]: unknown } | null | undefined // buildSchema が構築した SampleDate の処理内容を差し替え Object.assign(schema.getTypeMap().SampleDate, { serialize: (outputValue: unknown) => { console.log(`*** called serialize: ${outputValue}`) if (outputValue instanceof Date) { return outputValue.toISOString() } throw new GraphQLError('non Date') }, parseValue: (inputValue: unknown) => { console.log(`*** called parseValue: ${inputValue}`) if (typeof inputValue === 'string') { return toDate(inputValue) } throw new GraphQLError('non string value') }, parseLiteral: (valueNode: ValueNode, variables?: MaybeObjMap) => { console.log(`*** called parseLiteral: ${JSON.stringify(valueNode)}, ${JSON.stringify(variables)}`) if (valueNode.kind === Kind.STRING) { return toDate(valueNode.value) } throw new GraphQLError('non string value') } }) type DateInput = { date: Date } const rootValue = { now: () => new Date(), nextDay: ({ date }: DateInput) => new Date(date.getTime() + 24 * 60 * 60 * 1000) } const r1 = await graphql({ schema, rootValue, source: '{ now }' }) console.log(r1) console.log('-----') const r2 = await graphql({ schema, rootValue, source: '{ nextDay(date: "2022-10-21T13:00:00Z") }' }) console.log(r2) console.log('-----') const r3 = await graphql({ schema, rootValue, source: ` query ($d: SampleDate!) { nextDay(date: $d) } `, variableValues: { d: '2022-10-22T14:30:00Z' } }) console.log(r3)
実行結果は下記のようになりました。
parseLiteral は variables 引数の値が undefined
と {}
の場合の 2回呼び出されています。
また、変数(variableValues
)を使った場合は parseValue が呼び出されています。
実行結果
% deno run sample.ts *** called serialize: Fri Oct 21 2022 20:20:27 GMT+0900 (日本標準時) { data: { now: "2022-10-21T11:20:27.093Z" } } ----- *** called parseLiteral: {"kind":"StringValue","value":"2022-10-21T13:00:00Z","block":false,"loc":{"start":16,"end":38}}, undefined *** called parseLiteral: {"kind":"StringValue","value":"2022-10-21T13:00:00Z","block":false,"loc":{"start":16,"end":38}}, {} *** called serialize: Sat Oct 22 2022 22:00:00 GMT+0900 (日本標準時) { data: { nextDay: "2022-10-22T13:00:00.000Z" } } ----- *** called parseValue: 2022-10-22T14:30:00Z *** called serialize: Sun Oct 23 2022 23:30:00 GMT+0900 (日本標準時) { data: { nextDay: "2022-10-23T14:30:00.000Z" } }