2

これは、私が@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ます。ただし、部分ルールと汎用ルールを作成するには、新しいメソッドを追加する必要があります。

4

1 に答える 1