2

这与我为@nexus/schema库(类型安全的 GraphQL)构建的插件有关,但它纯粹是 Typescript 输入问题。

我有一个规则系统,我的所有规则都是从这个接口派生的:

interface Rule<Type extends string, Field extends string> {
  resolve(root: RootValue<Type>, args: ArgsValue<Type, Field>): boolean;
}

注意:RootValueandArgsValue是用于获取“真实”生成的类型或 return 的类型any,这是 nexus 用于键入所有内容的技巧,而无需显式指定类型。 请参阅此链接以获取源代码

最基本的两个是:

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)
}

我的问题是我需要能够支持适用于所有类型/字段的通用规则和仅适用于一种类型/字段的特定规则。但是,如果我将通用规则与特定规则结合起来,则生成的规则就是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,但是有没有一种解决方法可以用来防止类型 theany覆盖我的类型。我发现的一种解决方法是显式键入and,但从可用性的角度来看这并不好。

编辑 2:我创建了一个带有简化版本的游乐场,以便更容易查看问题

编辑 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

1

感谢@jcalz,我能够了解更多关于 Typescript 的打字系统的事情,并且基本上意识到即使我能够使用 nexus 的复杂助手使逆变工作,它也无法实现我想要做的事情。

所以我采取了另一种方法。它并不完美,但效果很好。我定义了两个新的运算符:

export const generic = (rule: Rule<any, any>) => <
  Type extends string,
  Field extends string
>(): Rule<Type, Field> => rule;

export const partial = <Type extends string>(rule: Rule<Type, any>) => <
  T extends Type, // NOTE: It would be best to do something with this type
  Field extends string
>(): Rule<Type, Field> => rule;

有了这些,返回的类型就变成了一个泛型函数。当您在“类型化上下文”中调用该函数时,它将阻止any.

const genericRule = generic(rule()((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(), //  Returns a Rule<"Test", "prop">
  myBadRule // ERROR
)

从某种意义上说,混合部分规则和通用规则非常冗长并且需要在父帮助程序中指定类型,这并不完美:

const myPartialType = partial<'Test'>(
  rule()((root, _args, ctx) => {
    return true;
  })
);

const myCombination = partial<'Test'>(
  chain(
    isAuthenticated(),
    myPartialType()
  )
);

我仍然觉得这有点像黑客,所以我仍然愿意接受建议和更好的解决方案。

于 2020-06-17T18:18:40.123 回答