英文:
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&importHelpers=true&allowSyntheticDefaultImports=true&noErrorTruncation=false&ts=4.6.4#code/JYWwDg9gTgLgBAbzgZQMYAsCmICGAaOABSgjDgF84AzEkOAIgAEA7TAZxgCs2B6ECZgHMIENpnoBuAFChIsRHACyEACaYANhWq0G-ISLGS4PHnBgBPMOwBccAKwA6AIwAmZwGYpU1Opxs2cACqYlAAIjAQiFJwcMw4IJi2HFDAQtLkXoxoWLgAFACU3r7%20QSFRMYzEpLlIUJgAjgCuwHUqtjBQjZgUhTFxCUkdqYLpXhZWRBCcEKEQqI0JzDAAPAAqAHxwALxwq3CYAB4wmMwqAUgAJOpzOOpsAPy2rABumFAScAD6n8%20PcDjMcwfT7AFR-AHmCjROD3XbQ2z0QjqTB%20bqoASveQwLBwFRzBYneARf5EADyAClSXBnsAcHAAAYOCKkgBGnEwqBgBXpcGgcAcyIB3MkXh8fgCwTebFCt3KcHRzGSjU50FyYBSzxwxzgjRCyjU6ls%20o0y0lUHW%20UQGWhfnMzFQ8rqWswuVQTuO4QgtjNnvytiqIGAYmWhCmM3xixWZvWmwQ0JiCo4cDqbEa6ngOxwAHccMAieggw5dW9jeoHG6UcdXe7ML7pPHjKY6jBGlBmMn2GmYEyIKz2ZyCtIYjETI39lASFBbAA9XaWboAcgAMijmLN5pHTWUAGRwdcEpbLCEECExhdwIOxCDwcXAQRxFnIsyRcaLxGCsTyjFvfPdPEbwlnxJQgKSpGk6UZZk2Q5Ll8h5PkBVXYUFwcLxhy-RV4EgaYACVO3TbYO1TdMez7GDBwbZtW3bbCIDw4iYCHYdRzeScZznCZl1XfdNzNOBdx4wkj0BE9ATPC8AmYa9-n8O8HyfYlXzgBd3xRT8FUxX9cQjQDiTpEDKWpWkGVI6CBzg3koH5QVmGQ1CYgya0xRKM02GQN4aVQbo4wTAQlRVKA1Q1Z0dRCaVbm9MKZXUS0EGtGJbXtR1KxdCtnU9SK3l9f1aCDTAtzeWMG0TeAUy7Qjs1zfNC2LKBwrLNKq0a2sInyD5RwASQXFR9gOKxOQZMr0x5YkWW6CAqDMecGVDaZBMPaN4Ks%20kzXpBtR3QzaYg6zCUR67FuiUoN2IDPLllU1EMM0swcX-A8iUifTQKMiDTP7WCluspC4PodZKMwFs2yIrsmMcqQgA
英文:
I've tried to create a PojoDocument<T>
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<T> = T & T extends { $locals?: never }
? T
: 'Please convert the document to a POJO via `.toObject()` or .lean()`';
My usage of the type looks like this:
class A {
...
async create(dto: CreateUserDto): Promise<PojoDocument<User>> {
const result = await this.userModel.create(dto);
return result.toObject();
// ^ Type 'LeanDocument<User & Document<any, any, User>>' is not assignable to type '"Please convert the document to a POJO via `.toObject()` or .lean()`"'.
}
}
The LeanDocument<User & Document<any, any, User>>
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<"Please convert the document to a POJO via `.toObject()` or .lean()`">
This means PojoDocument<T>
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<T> = T extends { $locals?: never; __v?: any; _id?: any }
? T
: 'Please convert the document to a POJO via `.toObject()` or .lean()`';
Any ideas how this check can be achieved?
Edit #1
I've added a minimal repro playground.
答案1
得分: 2
代码问题实际上出现在 UsersDal.create
,其中返回类型是:
Promise<PojoDocument<User>>
User
只有 name
字段,并不符合 PojoDocument
的要求,因此会返回字符串错误:
// type Test = "Please convert the document to a POJO via `.toObject()` or .lean()`"
type Test = PojoDocument<User>
然而,pojoResult
符合 PojoDocument
的条件,很明显,函数不应该返回字符串。
总结问题:
您的 create
函数期望返回一个字面字符串的 Promise,而不是 Promise 对象。
我不太熟悉 mongoose
,但我建议将 create
的返回类型更改为:
Promise<PojoDocument<LeanDocument<User & Document<any, any>>>>
完整的代码如下:
class UsersDal {
constructor(private userModel: Model<User>) {}
async create(createDto: UserDto): Promise<PojoDocument<LeanDocument<User & Document<any, any>>>> {
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<User> {
const result = await this.usersDal.create(createDto);
return result; // no error
}
}
英文:
The issue is actually with UsersDal.create
, where the return type is:
Promise<PojoDocument<User>>
User
has only name
and it doesn't meet the requirements of PojoDocument
, thus you get the string error returned:
// type Test = "Please convert the document to a POJO via `.toObject()` or .lean()`"
type Test = PojoDocument<User>
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<PojoDocument<LeanDocument<User & Document<any, any>>>>
Full code:
class UsersDal {
constructor(private userModel: Model<User>) {}
async create(createDto: UserDto): Promise<PojoDocument<LeanDocument<User & Document<any, any>>>> {
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<User> {
const result = await this.usersDal.create(createDto);
return result; // no error
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论