これは、私が@nexus/schemaライブラリ (タイプ セーフなGraphQL )用に構築しているプラグインに関連していますが、純粋に Typescript のタイピングの問題です。
すべてのルールがこのインターフェイスから派生するルール システムがあります。
interface Rule<Type extends string, Field extends string> {
resolve(root: RootValue<Type>, args: ArgsValue<Type, Field>): boolean;
}
注:RootValue
およびArgsValue
は、「実際の」生成された型または return を取得するために使用される型any
です。これは、型を明示的に指定する必要なく、すべてを型指定するために nexus が使用するトリックです。
ソース コードについては、このリンクを参照してください。
最も基本的なものは次の 2 つです。
type Options = { cache?: boolean }
type RuleFunc<Type extends string, Field extends string> =
(root: RootValue<Type>, args: ArgsValue<Type, Field>) => boolean;
class BaseRule<Type extends string, Field extends string> implements Rule<Type, Field> {
constructor(private options: Options, private func: RuleFunc<Type, Field>) {}
resolve(root: RootValue<Type>, args: ArgsValue<Type, Field>) {
// Do stuff with the options
const result = this.func(root, args)
return result
}
}
class AndRule<Type extends string, Field extends string> implements Rule<Type, Field> {
constructor(private rules: Rule<Type, Field>[]) { }
resolve(root: RootValue<Type>, args: ArgsValue<Type, Field>) {
return this.rules
.map(r => r.resolve(root, args))
.reduce((acc, val) => acc && val)
}
}
次に、ヘルパーを定義します。
const rule = (options?: Options) =>
<Type extends string, Field extends string>(func: RuleFunc<Type, Field>): Rule<Type, Field> => {
options = options || {};
return new BaseRule<Type, Field>(options, func);
};
const and = <Type extends string, Field extends string>(...rules: Rule<Type, Field>[]): Rule<Type, Field> => {
return new AndRule(rules)
}
私の問題は、すべてのタイプ/フィールドに適用される一般的なルールと、1 つのタイプ/フィールドにのみ適用される特定のルールをサポートできる必要があることです。しかし、一般的なルールを特定のルールと組み合わせると、結果として得られるルールは、Rule<any, any>
不適切なルールが受け入れられるようになります。
const genericRule = rule()<any, any>((root, args) => { return true; })
const myBadRule = rule()<"OtherType", "OtherField">((root, args) => {
return true;
})
const myRule: Rule<"Test", "prop"> = and(
rule()((root, args) => {
return false
}),
genericRule,
myBadRule // THIS SHOULD BE AN ERROR
)
Typescript に存在型の型付けがないことも関係していると思いますが、これは基本的に最初に a を使用することを強制しますが、型が型をオーバーライドany
するのを防ぐために使用できる回避策はありますか。any
私が見つけた 1 つの回避策は、 を明示的に入力するand
ことですが、これは使いやすさの観点からは良くありません。
編集 2:問題を簡単に表示できるように、簡略化されたバージョンのプレイグラウンドを作成しました。
EDIT 3:コメントで指摘されているようにnever
、前の例で動作します。このように、動作しないこの例を作成しましたnever
。また、後世のためにすべての情報が問題の中にあるように、問題を作り直しました。never
また、使用できない理由はArgsValue
タイプによるものであることがわかりました。
どうもありがとう!
編集1:
インターフェイスを変更する必要がありますが、回避策を見つけました。
export interface FullRule<
Type extends string,
Field extends string
> {
resolve(
root: RootValue<Type>,
args: ArgsValue<Type, Field>,
): boolean;
}
export interface PartialRule<Type extends string>
extends FullRule<Type, any> {}
export interface GenericRule extends FullRule<any, any> {}
export type Rule<Type extends string, Field extends string> =
| FullRule<TypeName, FieldName>
| PartialRule<TypeName>
| GenericRule;
にand
なると:
export const and = <Type extends string, Field extends string>(
...rules: Rule<Type, Field>[]
): FullRule<Type, Field> => {
return new RuleAnd<Type, Field>(rules);
};
は適切に型付けされたをand
返すため、FullRule<'MyType','MyField'>
を拒否しbadRule
ます。ただし、部分ルールと汎用ルールを作成するには、新しいメソッドを追加する必要があります。