Подтвердить что ты не робот

Mongoose Typescript способ...?

Попытка реализовать модель Mongoose в Typescript. Очистка Google выявила только гибридный подход (сочетающий JS и TS). Как можно было бы реализовать класс User, по моему довольно наивному подходу, без JS?

Хотите, чтобы 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() {}
}
4b9b3361

Ответ 1

Вот как я это делаю:

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;

Ответ 2

Другая альтернатива, если вы хотите отделить свои определения типов и реализацию базы данных.

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

Ответ 3

Извините за некропост, но кому-то это может быть интересно. Я думаю, Typegoose предоставляет более современный и элегантный способ определения моделей

Вот пример из документации:

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

Для существующего сценария подключения вы можете использовать следующее (что может быть более вероятным в реальных ситуациях и раскрыто в документации):

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

Ответ 4

Попробуйте ts-mongoose. Он использует условные типы для отображения.

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

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

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

Ответ 5

Просто добавьте другой способ:

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,
    // ...
}));

И разница между interface и type, пожалуйста, прочитайте этот ответ

Этот способ имеет преимущество, вы можете добавить типичные статические методы Mongoose:

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

Ответ 6

Если вы установили @types/mongoose

npm install --save-dev @types/mongoose

Вы можете так

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

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

PS: Скопировано @Hongbo Miao ответ

Ответ 7

Здесь строгий типизированный способ сопоставления простой модели со схемой мангуста. Компилятор гарантирует, что определения, переданные в mongoose.Schema, соответствуют интерфейсу. Как только у вас есть схема, вы можете использовать

common.ts

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]>
};

user.ts

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

Ответ 8

С этим vscode intellisense работает на обоих

  • Тип пользователя User.findOne
  • пользовательский экземпляр 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)

Ответ 9

Вот пример из документации Mongoose: Создание из классов ES6 с помощью loadClass(), преобразованного в 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, когда я хочу вызвать его. Если вы знаете, как это исправить, пожалуйста, добавьте комментарий.