graphql-fabbrica/先行事例の調査/graphql-codegen-typescript-mock-data のイマイチなところ
イマイチなところ一覧
デフォルトだと循環する GraphQL type があると実行時エラーになる
デフォルトでは field を辿って再帰的にモックデータを生成するので、循環する type があると Maximum call stack size exceeded になる。
code:schema.graphql
type A {
field1: Int!
b: B!
}
type B {
field2: Int!
a: A!
}
code:codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: './schema.graphql',
ignoreNoDocuments: true,
generates: {
'__generated__/graphql-codegen/types.ts': {
},
'__generated__/graphql-codegen/mocks.ts': {
config: {
typesFile: './types.ts',
},
},
},
};
export default config;
code:/__generated__/graphql-codegen/mocks.ts
// ...
export const anA = (overrides?: Partial<A>): A => {
return {
b: overrides && overrides.hasOwnProperty('b') ? overrides.b! : aB(),
field1: overrides && overrides.hasOwnProperty('field1') ? overrides.field1! : casual.integer(0, 9999),
};
};
export const aB = (overrides?: Partial<B>): B => {
return {
a: overrides && overrides.hasOwnProperty('a') ? overrides.a! : anA(),
field2: overrides && overrides.hasOwnProperty('field2') ? overrides.field2! : casual.integer(0, 9999),
};
};
code:test.ts
import { anA } from './__generated__/graphql-codegen/mocks';
const a1 = fakeA({
field1: 1,
b: fakeB(),
});
code:console
$ ts-node test.ts
file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:31
return {
^
RangeError: Maximum call stack size exceeded
at anA (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:31:5)
at aB (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:42:72)
at anA (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:33:72)
at aB (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:42:72)
at anA (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:33:72)
at aB (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:42:72)
at anA (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:33:72)
at aB (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:42:72)
at anA (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:33:72)
at aB (file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/__generated__/graphql-codegen/mocks.ts:42:72)
エラーメッセージを見ても、どの呼び出しが原因になったかすら分からないのがかなり厄介。test.tsからaB()を呼び出したのが実行時エラーの根本的原因だが、コールスタックにはtest.tsのログが1つも含まれていない。おそらくコールスタックが多すぎて見切れてしまっているのだろう。
terminateCircularRelationships: true の時、TypeScript の型上は non-nullable なのに null が入っていることがある
terminateCircularRelationships: trueにすると、循環したらそこでダミーデータの生成を打ち止めて、{}を返すようにできる。例えばA => B => A の循環参照があるときにconst a = anA()すると、a.b.aは{}になる。
ただし、graphql-codegen-typescript-mock-data が {} as Aで型エラーを握り潰すコードを生成するせいで、TypeScript のコードからはa.b.aが{}型ではなくA型に見える。そのため tsc の型検査には通るのに、実行時に型エラーになるコードが書けてしまう。
code:schema.graphql
type A {
field1: Int!
b: B!
}
type B {
field2: Int!
a: A!
}
code:codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: './schema.graphql',
ignoreNoDocuments: true,
generates: {
'__generated__/graphql-codegen/types.ts': {
},
'__generated__/graphql-codegen/mocks.ts': {
config: {
typesFile: './types.ts',
terminateCircularRelationships: true,
},
},
},
};
export default config;
code:/__generated__/graphql-codegen/mocks.ts
// ...
export const anA = (overrides?: Partial<A>, _relationshipsToOmit: Set<string> = new Set()): A => {
const relationshipsToOmit: Set<string> = new Set(_relationshipsToOmit);
relationshipsToOmit.add('A');
return {
b: overrides && overrides.hasOwnProperty('b') ? overrides.b! : relationshipsToOmit.has('B') ? {} as B : aB({}, relationshipsToOmit),
field1: overrides && overrides.hasOwnProperty('field1') ? overrides.field1! : 8857,
};
};
export const aB = (overrides?: Partial<B>, _relationshipsToOmit: Set<string> = new Set()): B => {
const relationshipsToOmit: Set<string> = new Set(_relationshipsToOmit);
relationshipsToOmit.add('B');
return {
a: overrides && overrides.hasOwnProperty('a') ? overrides.a! : relationshipsToOmit.has('A') ? {} as A : anA({}, relationshipsToOmit),
field2: overrides && overrides.hasOwnProperty('field2') ? overrides.field2! : 5140,
};
};
code:test.ts
import { anA } from './__generated__/graphql-codegen/mocks';
const a = anA({});
console.log(a.b.a.b.a);
code:console
$ ts-node test.ts
TypeError: Cannot read properties of undefined (reading 'a')
at file:///Users/mizdra/src/github.com/mizdra/playground-graphql-codegen-typescript-mock-data/test.ts:3:21
at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
理想的にはa.b.aが{}型になるよう厳密に型付けしてほしい。どの field でダミーデータの生成が打ち止められるかは静的にわかるから、理論上はできるはず。
nullalble な配列の field に null を割り当てる方法がない
[null]になってしまう。
code:schema.graphql
type A {
b: B!
}
type B {
field2: Int!
a: A!
}
code:codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: './schema.graphql',
ignoreNoDocuments: true,
generates: {
'__generated__/graphql-codegen/types.ts': {
},
'__generated__/graphql-codegen/mocks.ts': {
config: {
typesFile: './types.ts',
terminateCircularRelationships: true,
dynamicValues: true,
fieldGeneration: {
A: {
field1: 'null',
},
},
},
},
},
};
export default config;
code:test.ts
import { anA } from './__generated__/graphql-codegen/mocks';
const a = anA({});
console.log(a.field1); // expected null, actual [null]
field generator の書き心地が良くない
codegen.tsの中に文字列で TypeScript コードを書くので、エディタの補完が効かない
シンタックスハイライトが効かない
エディタ上に tsc の型エラーが出ない
field 間でデータの共有ができない
generateLibrary の関数の呼び出し方が特殊で戸惑う
generatorに関数名を、argumentsに引数を指定して呼び出す記法になってる
casual・faker-js と密結合になっている
generateLibrary の候補が casual/faker-js しかない
好きなダミーデータをランダムに生成するライブラリを使えない
しかも peerDeps ではなく deps になっている
好きなバージョンの casual・faker-js を使えない
どうなっていてほしいか
terminateCircularRelationships: true相当の挙動がデフォルトで、かつ厳密に型が付いてほしい
field generator はcodegen.tsの中に文字列で書くのではなく、好きな TypeScript ファイルに書けるようになっていてほしい
type ごとの field のデフォルト値はcodegen.tsではなく、ユーザの好きな TypeScript ファイルの中に書けるようにしたい
scalar のデフォルト値はダミーデータをランダムに生成するライブラリを使わず、固定値で埋めたい
number だったら 0
string だったら ""
boolean だったら false
custom scalar のデフォルト値を何にするかはプラグインの config で指定
2023/8/8 mizdra.icon どうなっていてほしいかのザックリとしたイメージを明らかにできたので、次はインターフェイスの細部を考える。