猫鼬的打字方式... ?

试图在 Typecript 中实现一个 Mongoose 模型。搜索谷歌只发现了一种混合的方法(结合了 JS 和 TS)。如果没有 JS,如何按照我相当天真的方法实现 User 类?

希望能够在没有包袱的情况下使用 IUserModel。

import {IUser} from './user.ts';
import {Document, Schema, Model} from 'mongoose';


// mixing in a couple of interfaces
interface IUserDocument extends IUser,  Document {}


// mongoose, why oh why '[String]'
// TODO: investigate out why mongoose needs its own data types
let userSchema: Schema = new Schema({
userName  : String,
password  : String,
firstName : String,
lastName  : String,
email     : String,
activated : Boolean,
roles     : [String]
});


// interface we want to code to?
export interface IUserModel extends Model<IUserDocument> {/* any custom methods here */}


// stumped here
export class User {
constructor() {}
}
110020 次浏览

我是这么做的:

export interface IUser extends mongoose.Document {
name: string;
somethingElse?: number;
};


export const UserSchema = new mongoose.Schema({
name: {type:String, required: true},
somethingElse: Number,
});


const User = mongoose.model<IUser>('User', UserSchema);
export default User;

如果要分离类型定义和数据库实现,则另一种选择。

import {IUser} from './user.ts';
import * as mongoose from 'mongoose';


type UserType = IUser & mongoose.Document;
const User = mongoose.model<UserType>('User', new mongoose.Schema({
userName  : String,
password  : String,
/* etc */
}));

灵感来源: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models

只需添加另一种方式(@types/mongoose必须与 npm install --save-dev @types/mongoose一起安装)

import { IUser } from './user.ts';
import * as mongoose from 'mongoose';


interface IUserModel extends IUser, mongoose.Document {}


const User = mongoose.model<IUserModel>('User', new mongoose.Schema({
userName: String,
password: String,
// ...
}));

interfacetype的区别,请读 this answer

这种方法有一个优点,您可以添加 Mongoose 静态方法类型:

interface IUserModel extends IUser, mongoose.Document {
generateJwt: () => string
}

很抱歉尸检,但这对某些人来说还是很有趣的。 I think 打字机 provides more modern and elegant way to define models

下面是文档中的一个例子:

import { prop, Typegoose, ModelType, InstanceType } from 'typegoose';
import * as mongoose from 'mongoose';


mongoose.connect('mongodb://localhost:27017/test');


class User extends Typegoose {
@prop()
name?: string;
}


const UserModel = new User().getModelForClass(User);


// UserModel is a regular Mongoose Model with correct types
(async () => {
const u = new UserModel({ name: 'JohnDoe' });
await u.save();
const user = await UserModel.findOne();


// prints { _id: 59218f686409d670a97e53e0, name: 'JohnDoe', __v: 0 }
console.log(user);
})();

For an existing connection scenario, you can use as the following (which may be more likely in the real situations and uncovered in the docs):

import { prop, Typegoose, ModelType, InstanceType } from 'typegoose';
import * as mongoose from 'mongoose';


const conn = mongoose.createConnection('mongodb://localhost:27017/test');


class User extends Typegoose {
@prop()
name?: string;
}


// Notice that the collection name will be 'users':
const UserModel = new User().getModelForClass(User, {existingConnection: conn});


// UserModel is a regular Mongoose Model with correct types
(async () => {
const u = new UserModel({ name: 'JohnDoe' });
await u.save();
const user = await UserModel.findOne();


// prints { _id: 59218f686409d670a97e53e0, name: 'JohnDoe', __v: 0 }
console.log(user);
})();

尝试 ts-mongoose。它使用条件类型来做映射。

import { createSchema, Type, typedModel } from 'ts-mongoose';


const UserSchema = createSchema({
username: Type.string(),
email: Type.string(),
});


const User = typedModel('User', UserSchema);

这里有一种强类型化的方法来匹配普通模型和猫鼬模式。编译器将确保将定义传递给猫鼬。模式与接口匹配。一旦您有了模式,您就可以使用

共同点

export type IsRequired<T> =
undefined extends T
? false
: true;


export type FieldType<T> =
T extends number ? typeof Number :
T extends string ? typeof String :
Object;


export type Field<T> = {
type: FieldType<T>,
required: IsRequired<T>,
enum?: Array<T>
};


export type ModelDefinition<M> = {
[P in keyof M]-?:
M[P] extends Array<infer U> ? Array<Field<U>> :
Field<M[P]>
};

用户

import * as mongoose from 'mongoose';
import { ModelDefinition } from "./common";


interface User {
userName  : string,
password  : string,
firstName : string,
lastName  : string,
email     : string,
activated : boolean,
roles     : Array<string>
}


// The typings above expect the more verbose type definitions,
// but this has the benefit of being able to match required
// and optional fields with the corresponding definition.
// TBD: There may be a way to support both types.
const definition: ModelDefinition<User> = {
userName  : { type: String, required: true },
password  : { type: String, required: true },
firstName : { type: String, required: true },
lastName  : { type: String, required: true },
email     : { type: String, required: true },
activated : { type: Boolean, required: true },
roles     : [ { type: String, required: true } ]
};


const schema = new mongoose.Schema(
definition
);

有了模式之后,就可以使用其他答案中提到的方法,例如

const userModel = mongoose.model<User & mongoose.Document>('User', schema);

有了这个 vscode intellisense两个都可以工作

  • User Type < em > User.findOne
  • 用户实例 < em > u1. _ id

守则:

// imports
import { ObjectID } from 'mongodb'
import { Document, model, Schema, SchemaDefinition } from 'mongoose'


import { authSchema, IAuthSchema } from './userAuth'


// the model


export interface IUser {
_id: ObjectID, // !WARNING: No default value in Schema
auth: IAuthSchema
}


// IUser will act like it is a Schema, it is more common to use this
// For example you can use this type at passport.serialize
export type IUserSchema = IUser & SchemaDefinition
// IUser will act like it is a Document
export type IUserDocument = IUser & Document


export const userSchema = new Schema<IUserSchema>({
auth: {
required: true,
type: authSchema,
}
})


export default model<IUserDocument>('user', userSchema)


下面是从 Mongoose 文档 使用 loadClass ()从 ES6类创建转换为 TypeScript 的示例:

import { Document, Schema, Model, model } from 'mongoose';
import * as assert from 'assert';


const schema = new Schema<IPerson>({ firstName: String, lastName: String });


export interface IPerson extends Document {
firstName: string;
lastName: string;
fullName: string;
}


class PersonClass extends Model {
firstName!: string;
lastName!: string;


// `fullName` becomes a virtual
get fullName() {
return `${this.firstName} ${this.lastName}`;
}


set fullName(v) {
const firstSpace = v.indexOf(' ');
this.firstName = v.split(' ')[0];
this.lastName = firstSpace === -1 ? '' : v.substr(firstSpace + 1);
}


// `getFullName()` becomes a document method
getFullName() {
return `${this.firstName} ${this.lastName}`;
}


// `findByFullName()` becomes a static
static findByFullName(name: string) {
const firstSpace = name.indexOf(' ');
const firstName = name.split(' ')[0];
const lastName = firstSpace === -1 ? '' : name.substr(firstSpace + 1);
return this.findOne({ firstName, lastName });
}
}


schema.loadClass(PersonClass);
const Person = model<IPerson>('Person', schema);


(async () => {
let doc = await Person.create({ firstName: 'Jon', lastName: 'Snow' });
assert.equal(doc.fullName, 'Jon Snow');
doc.fullName = 'Jon Stark';
assert.equal(doc.firstName, 'Jon');
assert.equal(doc.lastName, 'Stark');


doc = (<any>Person).findByFullName('Jon Snow');
assert.equal(doc.fullName, 'Jon Snow');
})();

对于静态 findByFullName方法,我无法指出如何获得类型信息 Person,所以当我想调用它时,我必须强制转换 <any>Person。如果你知道如何修复,请添加评论。

微软的人是这样做的

import mongoose from "mongoose";


export type UserDocument = mongoose.Document & {
email: string;
password: string;
passwordResetToken: string;
passwordResetExpires: Date;
...
};


const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
password: String,
passwordResetToken: String,
passwordResetExpires: Date,
...
}, { timestamps: true });


export const User = mongoose.model<UserDocument>("User", userSchema);

I recommend to check this excellent starter project out when you add TypeScript to your Node project.

Https://github.com/microsoft/typescript-node-starter

下面是 @types/mongoose包的一个基于 README 的示例。

除了上面已经包含的元素之外,它还展示了如何包含常规和静态方法:

import { Document, model, Model, Schema } from "mongoose";


interface IUserDocument extends Document {
name: string;
method1: () => string;
}
interface IUserModel extends Model<IUserDocument> {
static1: () => string;
}


var UserSchema = new Schema<IUserDocument & IUserModel>({
name: String
});


UserSchema.methods.method1 = function() {
return this.name;
};
UserSchema.statics.static1 = function() {
return "";
};


var UserModel: IUserModel = model<IUserDocument, IUserModel>(
"User",
UserSchema
);
UserModel.static1(); // static methods are available


var user = new UserModel({ name: "Success" });
user.method1();

总的来说,这个 README 似乎是用猫鼬接近类型的极好资源。

我是 Plumier 的粉丝,它有 猫鼬帮手but it can be used standalone without Plumier itself。与 Typeose 不同的是,它采用了 Plumier 专用的反射库,这使得它可以使用很酷的东西。

特征

  1. 纯 POJO (域不需要从任何类继承,也不需要使用任何特殊数据类型) ,自动创建的模型被推断为 T & Document,因此可以访问文档相关属性。
  2. 支持 TypeScript 参数属性,当您有 strict:true tsconfig 配置时这是很好的。使用参数属性时,不需要对所有属性使用修饰符。
  3. Supported field properties like Typegoose
  4. 配置是相同的猫鼬,所以你会很容易熟悉它。
  5. Supported inheritance that's make the programming more natural.
  6. 模型分析,显示模型名称及其相应的集合名称,应用的配置等。

用法

import model, {collection} from "@plumier/mongoose"




@collection({ timestamps: true, toJson: { virtuals: true } })
class Domain {
constructor(
public createdAt?: Date,
public updatedAt?: Date,
@collection.property({ default: false })
public deleted?: boolean
) { }
}


@collection()
class User extends Domain {
constructor(
@collection.property({ unique: true })
public email: string,
public password: string,
public firstName: string,
public lastName: string,
public dateOfBirth: string,
public gender: string
) { super() }
}


// create mongoose model (can be called multiple time)
const UserModel = model(User)
const user = await UserModel.findById()

这里的大多数答案重复 TypeScript 类/接口中的字段,以及猫鼬模式中的字段。由于项目变得更加复杂,并且有更多的开发人员致力于此,因此没有单一的真相来源意味着维护风险: 字段更容易失去同步。当类位于不同的文件中而非猫鼬模式时,这种情况尤其糟糕。

To keep fields in sync, it makes sense to define them once. There are a few libraries that do this:

我还没有完全相信他们中的任何一个,但是 typeose 似乎积极维护,并且开发人员接受了我的 PR。

提前考虑一步: 当您将 GraphQL 模式添加到混合中时,会出现另一层模型复制。克服这个问题的一种方法可能是从 GraphQL 模式转向 生成 TypeScript 和猫鼬代码

对于那些正在为现有的 Mongoose 项目寻找解决方案的人来说:

我们最近构建了 猫鼬来解决这个问题(希望得到一些反馈!).现有的解决方案如 typeose 需要重写整个模式并引入各种不兼容性。猫鼬是一个简单的 CLI 工具,它生成一个 index.d.ts 文件,其中包含所有 Mongoose 模式的类型脚本接口; 它几乎不需要配置,并且可以非常顺利地与任何类型脚本项目集成。

If you want to ensure that your schema satisfies the model type and vice versa , this solution offers better typing than what @bingles suggested:

通用类型文件: ToSchema.ts(不要惊慌! 只要复制粘贴它)

import { Document, Schema, SchemaType, SchemaTypeOpts } from 'mongoose';


type NonOptionalKeys<T> = { [k in keyof T]-?: undefined extends T[k] ? never : k }[keyof T];
type OptionalKeys<T> = Exclude<keyof T, NonOptionalKeys<T>>;
type NoDocument<T> = Exclude<T, keyof Document>;
type ForceNotRequired = Omit<SchemaTypeOpts<any>, 'required'> & { required?: false };
type ForceRequired = Omit<SchemaTypeOpts<any>, 'required'> & { required: SchemaTypeOpts<any>['required'] };


export type ToSchema<T> = Record<NoDocument<NonOptionalKeys<T>>, ForceRequired | Schema | SchemaType> &
Record<NoDocument<OptionalKeys<T>>, ForceNotRequired | Schema | SchemaType>;


以及一个示例模型:

import { Document, model, Schema } from 'mongoose';
import { ToSchema } from './ToSchema';


export interface IUser extends Document {
name?: string;
surname?: string;
email: string;
birthDate?: Date;
lastLogin?: Date;
}


const userSchemaDefinition: ToSchema<IUser> = {
surname: String,
lastLogin: Date,
role: String, // Error, 'role' does not exist
name: { type: String, required: true, unique: true }, // Error, name is optional! remove 'required'
email: String, // Error, property 'required' is missing
// email: {type: String, required: true}, // correct 👍
// Error, 'birthDate' is not defined
};


const userSchema = new Schema(userSchemaDefinition);


export const User = model<IUser>('User', userSchema);




最新的猫鼬软件包提供了打印支持。你不需要再使用“ type/monose”了。看看我的例子。

Https://jasonching2005.medium.com/complete-guide-for-using-typescript-in-mongoose-with-lean-function-e55adf1189dc

我发现以下方法是最简单和最有效的,因为它使用您定义的额外接口验证模式中的键,帮助您保持一切同步。

当您在模式上添加/更改模式验证器属性(如 maxlength、小写等)时,您还会得到令人惊异的类型脚本自动完成建议。

双赢!


import { Document, model, Schema, SchemaDefinitionProperty } from "mongoose";


type TDocument<Fields> = Fields & Document;
type TSchema<Fields> = Record<keyof Fields, SchemaDefinitionProperty>;


type UserFields = {
email: string;
firstName?: string;
roles?: string[];
};


const userSchema: TSchema<UserFields> = {
email: { type: Schema.Types.String, required: true, index: true },
firstName: { type: Schema.Types.String, maxlength: 30, trim: true },
roles: [
{ type: Schema.Types.String, maxlength: 20, lowercase: true },
],
};


export const User = model<TDocument<UserFields>>(
"User",
new Schema(userSchema, { timestamps: true })
);


最棒的是,您可以为所有模型重用 TDocument 和 TSchema 类型。

不知道这是不是你要找的 但是有一个包叫做“ a href = “ https://Typegoose.github.io/”rel = “ nofollow norefrer”> Typeose

TypeORM is a better and 现代的 solution. It supports both JavaScript and 打字机.

TypeORM 是一种 ORM,可以在 NodeJS、 Browser、 Cordova、 PhoneGap、 Ionic、 React ativeScript、 NativeScript、 Expo 和 Electronic 平台上运行,可以与 TypeScript 和 JavaScript (ES5、 ES6、 ES7、 ES8)一起使用。

它有很多 特征

它的目标是始终支持最新的 JavaScript 特性,并提供额外的特性来帮助您开发使用数据库的任何类型的应用程序——从只有几个表的小型应用程序到具有多个数据库的大型企业应用程序。

它支持大多数数据库,如 mysqlmariadbpostgrescockroachdbsqlitemssqloracle等以及 mongodb

TypeORM 同时支持活动记录和数据映射器模式 当前存在的所有其他 JavaScript ORM,这意味着您可以 编写高质量、松散耦合、可伸缩、可维护的 最有效的应用方式。

因此,不需要为不同的数据库学习不同的 ORM 或框架。

正式文件不鼓励 TS 接口扩展文件。

这种方法可以工作,但我们建议您的文档接口不要扩展 Document。使用扩展文档使得 Mongoose 很难推断查询过滤器、精益文档和其他情况下存在哪些属性。

TS 接口

export interface IPerson {
firstName: string;
lastName: string;
fullName: string;
}

Schema

    const personSchema = new Schema<IPerson>({
//You get intellisense of properties so less error prone
firstName:{type:String},
lastName:{type:String}
})


personSchema.virtual('fullName').get(function(this:IPerson) {
return this.firstName + " " this.lastName
});


export const User = model<IPerson>('person',personSchema)

Well, I found the following link really really helpful where the author has described each and every step in details without using any library.

带 MongoDB 和 Node/Express 的类型脚本

This has really really helped me and hoping will be very helpful for those searching for a solution without installing any extra plugin.

但是,如果你喜欢你可以尝试 TypeORM类型鹅

But I prefer to go without installing any library :-).

Mongoose introduced officially supported TypeScript bindings in v5.11.0. Https://mongoosejs.com/docs/TypeScript.html 描述了 Mongoose 用 TypeScript 处理 Mongoose 的推荐方法。

2022年最新情况

输入文档的新推荐方法是使用单一接口。要在应用程序中输入文档,应该使用 HydratedDocument:

import { HydratedDocument, model, Schema } from "mongoose";


interface Animal {
name: string;
}


const animalSchema = new Schema<Animal>({
name: { type: String, required: true },
});


const AnimalModel = model<Animal>("Animal", animalSchema);


const animal: HydratedDocument<Animal> = AnimalModel.findOne( // ...

猫鼬建议不要扩展文档。

Https://mongoosejs.com/docs/typescript.html