如何创建一个 TypeScript 自定义类型错误以及默认类型?

huangapple go评论61阅读模式
英文:

How to create a typescript custom type error and default type for it?

问题

我试图创建一个PojoDocument<T>泛型类型,以防止将mongoose文档泄露到我们的DALs。

我的目标是确保返回的对象不是mongoose Document,而是一个POJO(PojoDocument)。
我通过检查T永远不能具有$locals属性来实现这一目标,因为它在所有文档中都存在:

// 如果`T`具有`$locals`属性,则将字符串解析为类型,这将与`T`冲突并显示为错误。
// 因此,当`$locals`属性不存在时,将返回`T`作为类型。
export type PojoDocument<T> = T extends { $locals?: never }
  ? T
  : '请通过`.toObject()`或`.lean()`将文档转换为POJO';

我的类型的用法如下:

class A {
...
  async create(dto: CreateUserDto): Promise<PojoDocument<User>> {
    const result = await this.userModel.create(dto);
    return result.toObject();
    // ^ 类型 'LeanDocument<User & Document<any, any, User>>' 不能赋值给类型 '"请通过`.toObject()`或`.lean()`将文档转换为POJO"'。
  }
}

LeanDocument<User & Document<any, any, User>>类型等于:

interface ResultToObjectType {
  __v?: any;
  _id?: any;
  name: string;
}

此外,当我检查在哪里使用create方法时返回的内容时,我得到了这个:

Promise<"请通过`.toObject()`或`.lean()`将文档转换为POJO">

这意味着PojoDocument<T>默认为“else”条件,而不是提供给它的T

我尝试修改类型以包括__v_id,认为这是它不认为类型被$locals?: never扩展的原因,因为这些属性在T上不存在,但这并没有改变任何事情:

export type PojoDocument<T> = T extends { $locals?: never; __v?: any; _id?: any }
  ? T
  : '请通过`.toObject()`或`.lean()`将文档转换为POJO';

有没有办法实现这个检查呢?

编辑 #1

我已经添加了一个最小可重现的演示
1: https://www.typescriptlang.org/play?strictPropertyInitialization=false&amp;importHelpers=true&amp;allowSyntheticDefaultImports=true&amp;noErrorTruncation=false&amp;ts=4.6.4#code/JYWwDg9gTgLgBAbzgZQMYAsCmICGAaOABSgjDgF84AzEkOAIgAEA7TAZxgCs2B6ECZgHMIENpnoBuAFChIsRHACyEACaYANhWq0G-ISLGS4PHnBgBPMOwBccAKwA6AIwAmZwGYpU1Opxs2cACqYlAAIjAQiFJwcMw4IJi2HFDAQtLkXoxoWLgAFACU3r7%20QSFRMYzEpLlIUJgAjgCuwHUqtjBQjZgUhTFxCUkdqYLpXhZWRBCcEKEQqI0JzDAAPAAqAHxwALxwq3CYAB4wmMwqAUgAJOpzOOpsAPy2rABumFAScAD6n8%20PcDjMcwfT7AFR-AHmCjROD3XbQ2z0QjqTB%20bqoASveQwLBwFRzBYneARf5EADyAClSXBnsAcHAAAYOCKkgBGnEwqBgBXpcGgcAcyIB3MkXh8fgCwTebFCt3KcHRzGSjU50FyYBSzxwxzgjRCyjU6ls%20o0y0lUHW%20UQGWhfnMzFQ8rqWswuVQTuO4QgtjNnvytiqIGAYmWhCmM3xixWZvWmwQ0JiCo4cDqbEa6ngOxwAHccMAieggw5dW9jeoHG6UcdXe7ML7pPHjKY6jBGlBmMn2GmYEyIKz2ZyCtIYjETI39lASFBbAA9XaWboAcgAMijmLN5pHTWUAGRwdcEpbLCEECExhdwIOxCDwcXAQRxFnIsyRcaLxGCsTyjFvfPdPEbwlnxJQgKSpGk6UZZk2Q5Ll8h5PkBVXYUFwcLxhy-RV4EgaYACVO3TbYO1TdMez7GDBwbZtW3bbCIDw4iYCHYdRzeScZznCZl1XfdNzNOBdx4wkj0BE9ATPC8AmYa9-n8O8HyfYlXzgBd3xRT8FUxX9cQjQDiTpEDKWpWkGVI6CBzg3koH5QVmGQ1CYgya0xRKM02GQN4aVQbo4wTAQlRVKA1Q1Z0dRCaVbm9MKZXUS0EGtGJbXtR1KxdCtnU9SK3l9f1aCDTAtzeWMG0TeAUy7Qjs1zfNC2LKBwrLNKq0a2sInyD5RwASQXFR9gOKxOQZMr0x5YkWW6CAqDMecGVDaZBMPaN4Ks%20kzXpBtR3QzaYg6zCUR67FuiUoN2IDPLllU1EMM0swcX-A8iUifTQKMiDTP7WCluspC4PodZKMwFs2yIrsmMcqQgA

英文:

I've tried to create a PojoDocument&lt;T&gt; generic type to guard from leaking mongoose documents out of our DALs.

My goal was to make sure the returned object is not a mongoose Document, but a POJO(PojoDocument).
I've done it by checking that T can never have the $locals property, as it's found on all documents:

// If `T` has `$locals` property, then resolve the string as the type, which will clash with `T` and show as an error. 
// So when `$locals` property not found, `T` will be returned as the type. 
export type PojoDocument&lt;T&gt; = T &amp; T extends { $locals?: never }
  ? T
  : &#39;Please convert the document to a POJO via `.toObject()` or .lean()`&#39;;

My usage of the type looks like this:

class A {
...
  async create(dto: CreateUserDto): Promise&lt;PojoDocument&lt;User&gt;&gt; {
    const result = await this.userModel.create(dto);
    return result.toObject();
    // ^ Type &#39;LeanDocument&lt;User &amp; Document&lt;any, any, User&gt;&gt;&#39; is not assignable to type &#39;&quot;Please convert the document to a POJO via `.toObject()` or .lean()`&quot;&#39;.
  }
}

The LeanDocument&lt;User &amp; Document&lt;any, any, User&gt;&gt; type is equals to:

interface ResultToObjectType {
  __v?: any;
  _id?: any;
  name: string;
}

Also, when I check what is returned by the create method where it's used, I get this:

Promise&lt;&quot;Please convert the document to a POJO via `.toObject()` or .lean()`&quot;&gt;

This means PojoDocument&lt;T&gt; defaults to the "else" condition instead of the T that was provided for it.

I've tried modifying the type to include __v and _id, thinking that's the reason it doesn't consider the type as being extended by $locals?: never, as these properties don't exist on T, but it didn't change anything:

export type PojoDocument&lt;T&gt; = T extends { $locals?: never; __v?: any; _id?: any }
  ? T
  : &#39;Please convert the document to a POJO via `.toObject()` or .lean()`&#39;;

Any ideas how this check can be achieved?

Edit #1

I've added a minimal repro playground.

答案1

得分: 2

代码问题实际上出现在 UsersDal.create,其中返回类型是:

Promise&lt;PojoDocument&lt;User&gt;&gt;

User 只有 name 字段,并不符合 PojoDocument 的要求,因此会返回字符串错误:

// type Test = &quot;Please convert the document to a POJO via `.toObject()` or .lean()`&quot;
type Test = PojoDocument&lt;User&gt;

然而,pojoResult 符合 PojoDocument 的条件,很明显,函数不应该返回字符串。

总结问题:

您的 create 函数期望返回一个字面字符串的 Promise,而不是 Promise 对象。

我不太熟悉 mongoose,但我建议将 create 的返回类型更改为:

Promise&lt;PojoDocument&lt;LeanDocument&lt;User &amp; Document&lt;any, any&gt;&gt;&gt;&gt;

完整的代码如下:

class UsersDal {
  constructor(private userModel: Model&lt;User&gt;) {}

  async create(createDto: UserDto): Promise&lt;PojoDocument&lt;LeanDocument&lt;User &amp; Document&lt;any, any&gt;&gt;&gt;&gt; {
    const result = await this.userModel.create(createDto);

    const pojoResult = result.toObject();
    return pojoResult; // no error
  }
}

class UsersService {
  constructor(private usersDal: UsersDal) {}

  async create(createDto: UserDto): Promise&lt;User&gt; {
    const result = await this.usersDal.create(createDto);
    return result; // no error
  }
}

playground

英文:

The issue is actually with UsersDal.create, where the return type is:

Promise&lt;PojoDocument&lt;User&gt;&gt;

User has only name and it doesn't meet the requirements of PojoDocument, thus you get the string error returned:

// type Test = &quot;Please convert the document to a POJO via `.toObject()` or .lean()`&quot;
type Test = PojoDocument&lt;User&gt;

However, pojoResult does obey the conditions of PojoDocument and obviously, it's not the string that is expected to be returned by the function.

Summarizing the issue:

Your create expects a promise of a literal string to be returned, not the promise object.

I'm not familiar with mongoose, but I can suggest changing the return type of the create to:

Promise&lt;PojoDocument&lt;LeanDocument&lt;User &amp; Document&lt;any, any&gt;&gt;&gt;&gt;

Full code:

class UsersDal {
  constructor(private userModel: Model&lt;User&gt;) {}

  async create(createDto: UserDto): Promise&lt;PojoDocument&lt;LeanDocument&lt;User &amp; Document&lt;any, any&gt;&gt;&gt;&gt; {
    const result = await this.userModel.create(createDto);

    const pojoResult = result.toObject();
    return pojoResult; // no error
  }
}

class UsersService {
  constructor(private usersDal: UsersDal) {}

  async create(createDto: UserDto): Promise&lt;User&gt; {
    const result = await this.usersDal.create(createDto);
    return result; // no error
  }
}

playground

huangapple
  • 本文由 发表于 2023年6月18日 19:45:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76500371.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定