【发布时间】:2020-10-03 09:08:21
【问题描述】:
这与我正在为 @nexus/schema 库(类型安全的 GraphQL)构建的 plugin 有关,但这纯粹是 Typescript 键入问题。
我有一个规则系统,我的所有规则都是从这个接口派生的:
interface Rule<Type extends string, Field extends string> {
resolve(root: RootValue<Type>, args: ArgsValue<Type, Field>): boolean;
}
注意:RootValue 和ArgsValue 是用于获取“真实”生成类型或返回any 的类型,这是nexus 用于键入所有内容的技巧,无需显式指定类型。
See this link for the source code.
最基本的两个是:
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 中缺乏存在类型,这基本上迫使我首先使用 any,但是有没有一种解决方法可以用来防止 @ 的类型987654338@ 覆盖我的类型。我发现的一种解决方法是显式键入and,但从可用性的角度来看这并不好。
编辑 2:我创建了一个 playground with a simplified version so it easier to view the problem。
编辑 3: 正如 cmets 中所指出的,never 与前面的示例一起使用。 I thus created this example for which never does not work。我还修改了这个问题,以便所有信息都在问题中以供后代使用。我也发现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。但它确实需要我添加新方法来创建部分规则和通用规则。
【问题讨论】:
-
请考虑将示例代码转换为minimal reproducible example,可以将其放入The TypeScript Playground 等独立IDE 中,以展示您所看到的内容。或者,可以添加一个链接到已经处理了库依赖项的现有 Web IDE?
-
老实说,我真的尝试过在codesanbox中显示有点困难,最好是在这里运行我的示例:github.com/Sytten/nexus-shield/tree/master/examples/simple
-
我不知道你能指望别人在他们的本地系统上安装项目;我个人不太可能这样做。
-
@jcalz 我创建了一个带有简化版问题的游乐场(请参阅帖子中的编辑)。很抱歉在我之前的回复中是个混蛋。如果您能看一看,我将不胜感激!
-
Blecch,那些关系助手需要大量的条件输入。我看到的问题是编译器不接受
Rule<A, B>在A和B中是逆变的,尽管我认为它应该是。大多数“不错”的解决方案将涉及使用Rule<string, string>作为通用规则,而and()自然只会接受正确的类型。但这并没有发生,可能是因为那些助手中的东西。很难看到如何在不扔掉这些东西的情况下修复它。 ??????
标签: typescript strong-typing nexus-prisma