Scalar

Scalar(スカラー)とは

GraphQLのオブジェクトタイプは名前とフィールドをそれぞれ持ちますが、ある時点でこれらのフィールドは具体的なデータ型で表示される必要があります。そこで登場するのがScalarです。
GraphQLには以下のようなデフォルトの型があります。IntFloatStringBooleanIDの5つの型があります。これらの組み込み型に加えて、カスタムのデータ型をサポートしています。

コードファースト

コードファーストのアプローチでは、5つのScalarが同梱されており、そのうち3つは既存のGraphQL型の単純なエイリアスになっています。
  • ID:一意の識別子を表し、しばしばオブジェクトの再取得やキャッシュのキーとして使われる。
  • Int:符号付きの32ビット整数。
  • Float:符号付きの倍精度浮動小数点値。
  • GraphQLISODateTime:UTCでの日付時間文字列。(デフォルトでDate型を表現するのに使われる)
  • GraphQLTimeStamp:日付と時刻をUNIXエポックの開始からのミリ秒数で表現する符号付き整数。
GraphQLISODateTime(例:2019-12-03T09:54:33Z)は、デフォルトでDate型を表すのに使用されます。代わりに GraphQLTimestampを使用するには、次のように buildSchemaOptionsオブジェクトのdateScalarMode'timestamp'に設定します。
GraphQLModule.forRoot({
buildSchemaOptions: {
dateScalarMode: 'timestamp',
}
}),
同様に、GraphQLFloatはデフォルトで数値型を表現するために使用されます。代わりにGraphQLIntを使用するには、次のようにbuildSchemaOptionsオブジェクトのnumberScalarMode'integer'に設定します。
GraphQLModule.forRoot({
buildSchemaOptions: {
numberScalarMode: 'integer',
}
}),
加えて、カスタムのScalarを作成できます。

デフォルトのScalarを継承する

Dateスカラーのカスタム実装を作成するには、単に新しいクラスを作成します。
import { Scalar, CustomScalar } from '@nestjs/graphql';
import { Kind, ValueNode } from 'graphql';
@Scalar('Date', (type) => Date)
export class DateScalar implements CustomScalar<number, Date> {
description = 'Date custom scalar type';
parseValue(value: number): Date {
return new Date(value); // value from the client
}
serialize(value: Date): number {
return value.getTime(); // value sent to the client
}
parseLiteral(ast: ValueNode): Date {
if (ast.kind === Kind.INT) {
return new Date(ast.value);
}
return null;
}
}
この状態で、DateScalarをプロバイダとして登録します。
@Module({
providers: [DateScalar],
})
export class CommonModule {}
これで、クラスの中でDate型を使えるようになりました。
@Field()
creationDate: Date;

カスタムのScalarをインポートする

カスタムスカラーを使うには、インポートしてリゾルバとして登録します。ここでは、デモ用にgraphql-type-jsonパッケージを使用することにします。このnpmパッケージは、JSONのGraphQLスカラータイプを定義します。
まず、このパッケージをインストールすることから始めましょう。
npm i --save graphql-type-json
パッケージがインストールされたら、forRoot()メソッドにカスタムリゾルバを渡します。
import GraphQLJSON from 'graphql-type-json';
@Module({
imports: [
GraphQLModule.forRoot({
resolvers: { JSON: GraphQLJSON },
}),
],
})
export class AppModule {}
これで、JSON型をクラスで使えるようになりました。
@Field((type) => GraphQLJSON)
info: JSON;

カスタムのスカラーを作成する

カスタムのスカラーを定義するには、新しいGraphQLScalarTypeインスタンスを作成します。ここでは、カスタムUUIDスカラーを作成します。
const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function validate(uuid: unknown): string | never {
if (typeof uuid !== "string" || !regex.test(uuid)) {
throw new Error("invalid uuid");
}
return uuid;
}
export const CustomUuidScalar = new GraphQLScalarType({
name: 'UUID',
description: 'A simple UUID parser',
serialize: (value) => validate(value),
parseValue: (value) => validate(value),
parseLiteral: (ast) => validate(ast.value)
})
forRoot()にカスタムリゾルバを渡します。
@Module({
imports: [
GraphQLModule.forRoot({
resolvers: { UUID: CustomUuidScalar },
}),
],
})
export class AppModule {}
これで、クラス経由でUUID型を使えるようになりました。
@Field((type) => CustomUuidScalar)
uuid: string;

スキーマファースト

カスタムのスカラーを定義するには、型定義と専用のリゾルバを作成します。ここでは、公式ドキュメントと同様にデモ用にgraphql-type-jsonパッケージを使用することにします。
このnpmパッケージはJSONGraphQLスカラー型を定義します。まずはこのパッケージをインストールしましょう。
$ npm i --save graphql-type-json
パッケージがインストールされたら、forRoot()メソッドにカスタムリゾルバを渡します。
import GraphQLJSON from 'graphql-type-json';
@Module({
imports: [
GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
resolvers: { JSON: GraphQLJSON },
}),
],
})
export class AppModule {}
これで、JSONスカラーを型定義で使えるようになりました。
scalar JSON
type Foo {
field: JSON
}
スカラー型を定義するもう一つの方法は、単純なクラスを作成することです。Date型でスキーマを拡張する場合を考えてみましょう。
import { Scalar, CustomScalar } from '@nestjs/graphql';
import { Kind, ValueNode } from 'graphql';
@Scalar('Date')
export class DateScalar implements CustomScalar<number, Date> {
description = 'Date custom scalar type';
parseValue(value: number): Date {
return new Date(value); // value from the client
}
serialize(value: Date): number {
return value.getTime(); // value sent to the client
}
parseLiteral(ast: ValueNode): Date {
if (ast.kind === Kind.INT) {
return new Date(ast.value);
}
return null;
}
}
この状態で、DateScalarをプロバイダとして登録します。
@Module({
providers: [DateScalar],
})
export class CommonModule {}
これで、Dateスカラーを型定義に使えるようになりました。
scalar Date
デフォルトでは、すべてのスカラーに対して生成されるTypeScriptの定義はanyであり、特にタイプセーフというわけでもありません。ところが、型の生成方法を指定することでNestがカスタムスカラーの型付けをどのように生成するかを設定できます。
import { GraphQLDefinitionsFactory } from '@nestjs/graphql';
import { join } from 'path';
const definitionsFactory = new GraphQLDefinitionsFactory();
definitionsFactory.generate({
typePaths: ['./src/**/*.graphql'],
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class',
defaultScalarType: 'unknown',
customScalarTypeMapping: {
DateTime: 'Date',
BigNumber: '_BigNumber',
},
additionalHeader: "import _BigNumber from 'bignumber.js'",
});
メモ
また、代わりに型参照を使うこともできます。DateTime:Date.Nameのように型参照を使うこともできます。この場合、GraphQLDefinitionsFactory は指定された型のnameプロパティ(Date.name)を抽出して TS 定義を生成します。注:ビルトインでない型(カスタム型)については、import文の追加が必要です。
さて、以下のようなGraphQLのカスタムスカラー型が与えられたと仮定しましょう。
scalar DateTime
scalar BigNumber
scalar Payload
これで、src/graphql.tsに以下のようなTypeScriptの定義が生成されたことが確認できます。
import _BigNumber from 'bignumber.js';
export type DateTime = Date;
export type BigNumber = _BigNumber;
export type Payload = unknown;
ここでは、customScalarTypeMappingプロパティを使用して、カスタム・スカラー用に宣言したい型のマップを提供します。また、additionalHeaderプロパティも用意し、これらのタイプ定義に必要なインポートを追加できるようにした。最後に、defaultScalarType'unknown'を追加し、customScalarTypeMappingで指定されていないカスタムスカラーはanyの代わりにunknownにエイリアスされるようにした(TypeScript では ver3.0 から型の安全性を高めるためにこの使用が推奨されています)。
メモ
bignumber.jsから_BigNumberをインポートしていることに注意してください。これは、循環的な型参照を避けるためです。
Last modified 10mo ago