Mencoba mengimplementasikan model Mongoose di Typecript. Menjelajahi Google hanya mengungkapkan pendekatan hibrid (menggabungkan JS dan TS). Bagaimana cara menerapkan kelas User, dengan pendekatan saya yang agak naif, tanpa JS?
Ingin dapat IUserModel tanpa bagasi.
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() {}
}
javascript
node.js
mongoose
typescript
Tim McNamara
sumber
sumber
User
tidak bisa menjadi kelas karena membuatnya adalah operasi asinkron. Itu harus membalas janji jadi Anda harus meneleponUser.create({...}).then...
.User
tidak bisa menjadi kelas?Jawaban:
Begini cara saya melakukannya:
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;
sumber
import * as mongoose from 'mongoose';
atauimport mongoose = require('mongoose');
import User from '~/models/user'; User.find(/*...*/).then(/*...*/);
let newUser = new User({ iAmNotHere: true })
tanpa kesalahan dalam IDE atau saat kompilasi. Jadi apa alasan untuk membuat antarmuka?Alternatif lain jika Anda ingin melepaskan definisi tipe dan implementasi database.
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 */ }));
Inspirasi dari sini: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models
sumber
mongoose.Schema
definisi di sini menduplikasi bidang dariIUser
? Mengingat ituIUser
didefinisikan dalam file yang berbeda , risiko bidang yang tidak sinkron seiring dengan pertumbuhan proyek dalam kompleksitas dan jumlah pengembang, cukup tinggi.Maaf untuk necroposting tapi ini bisa tetap menarik untuk seseorang. Saya pikir Typegoose memberikan cara yang lebih modern dan elegan untuk menentukan model
Berikut ini contoh dari dokumen:
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); })();
Untuk skenario koneksi yang sudah ada, Anda dapat menggunakan sebagai berikut (yang mungkin lebih mungkin terjadi dalam situasi nyata dan ditemukan di dokumen):
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); })();
sumber
typegoose
tidak memiliki cukup dukungan ... memeriksa statistik npm mereka, itu hanya unduhan mingguan 3k, dan ada hampir 100 masalah Github terbuka, sebagian besar tidak memiliki komentar, dan beberapa di antaranya sepertinya sudah lama ditutuptypegoose
- kami akhirnya menangani pengetikan kami secara manual, mirip dengan posting ini , sepertinyats-mongoose
mungkin ada beberapa janji (seperti yang disarankan dalam jawaban nanti)Coba
ts-mongoose
. Ini menggunakan tipe kondisional untuk melakukan pemetaan.import { createSchema, Type, typedModel } from 'ts-mongoose'; const UserSchema = createSchema({ username: Type.string(), email: Type.string(), }); const User = typedModel('User', UserSchema);
sumber
Sebagian besar jawaban di sini mengulangi bidang di kelas / antarmuka TypeScript, dan di skema luwak. Tidak memiliki satu sumber kebenaran mewakili risiko pemeliharaan, karena proyek menjadi lebih kompleks dan lebih banyak pengembang yang mengerjakannya: bidang lebih mungkin tidak sinkron . Ini sangat buruk ketika kelas berada dalam file yang berbeda vs. skema luwak.
Untuk menjaga bidang tetap sinkron, masuk akal untuk menetapkannya sekali. Ada beberapa perpustakaan yang melakukan ini:
Saya belum sepenuhnya yakin dengan salah satu dari mereka tetapi tipikal kambing tampaknya dipertahankan secara aktif, dan pengembang menerima PR saya.
Untuk berpikir selangkah lebih maju: ketika Anda menambahkan skema GraphQL ke dalam campuran, lapisan duplikasi model lain muncul. Salah satu cara untuk mengatasi masalah ini mungkin dengan menghasilkan TypeScript dan kode luwak dari skema GraphQL.
sumber
Berikut adalah cara mengetik yang kuat untuk mencocokkan model biasa dengan skema luwak. Kompilator akan memastikan definisi yang diteruskan ke mongoose.Schema cocok dengan antarmuka. Setelah Anda memiliki skema, Anda dapat menggunakan
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 );
Setelah Anda memiliki skema, Anda dapat menggunakan metode yang disebutkan dalam jawaban lain seperti
const userModel = mongoose.model<User & mongoose.Document>('User', schema);
sumber
Cukup tambahkan cara lain (
@types/mongoose
harus dipasang dengannpm 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, // ... }));
Dan perbedaan antara
interface
dantype
, harap baca jawaban iniCara ini memiliki keuntungan, Anda dapat menambahkan pengetikan metode statis Mongoose:
interface IUserModel extends IUser, mongoose.Document { generateJwt: () => string }
sumber
generateJwt
?const User = mongoose.model.... password: String, generateJwt: () => { return someJwt; } }));
pada dasarnya,generateJwt
menjadi properti lain dari model.IUser
deklarasi antarmuka dalam file yang berbeda adalah bahwa risiko bidang tidak sinkron seiring pertumbuhan proyek dalam jumlah kompleksitas dan pengembang, cukup tinggi.Inilah cara orang-orang di Microsoft melakukannya. sini
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);
Saya sarankan untuk memeriksa proyek pemula yang luar biasa ini ketika Anda menambahkan TypeScript ke proyek Node Anda.
https://github.com/microsoft/TypeScript-Node-Starter
sumber
ts-mongoose
dantypegoose
memecahkan masalah itu, meskipun diakui dengan sedikit sintaksis cruft.Dengan ini vscode intellisensebekerja pada keduanya
Kode:
// 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)
sumber
Berikut ini contoh dari dokumentasi Mongoose, Membuat dari ES6 Classes Using loadClass () , dikonversi ke 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'); })();
Untuk
findByFullName
metode statis , saya tidak tahu bagaimana mendapatkan informasi tipePerson
, jadi saya harus melakukan cast<any>Person
ketika saya ingin memanggilnya. Jika Anda tahu cara memperbaikinya, silakan tambahkan komentar.sumber
ts-mongoose
atautypegoose
. Situasi ini semakin terduplikasi saat mendefinisikan skema GraphQL.Saya penggemar Plumier, ia memiliki penolong luwak , tetapi dapat digunakan secara mandiri tanpa Plumier itu sendiri . Tidak seperti Typegoose yang mengambil jalur berbeda dengan menggunakan perpustakaan refleksi khusus Plumier, yang memungkinkan untuk menggunakan hal-hal keren.
fitur
T & Document
sehingga memungkinkan untuk mengakses properti terkait dokumen.strict:true
konfigurasi tsconfig. Dan dengan properti parameter tidak membutuhkan dekorator pada semua properti.Pemakaian
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()
sumber
Untuk siapa saja yang mencari solusi untuk proyek Mongoose yang sudah ada:
Kami baru-baru ini membuat luwak-tsgen untuk mengatasi masalah ini (kami akan sangat menyukai umpan balik!). Solusi yang ada seperti typegoose mengharuskan penulisan ulang seluruh skema kami dan menimbulkan berbagai ketidakcocokan. mongoose-tsgen adalah alat CLI sederhana yang menghasilkan file index.d.ts yang berisi antarmuka Typecript untuk semua skema Mongoose Anda; itu membutuhkan sedikit atau tanpa konfigurasi dan terintegrasi dengan sangat lancar dengan proyek Typecript.
sumber
Berikut adalah contoh berdasarkan README untuk
@types/mongoose
paket tersebut.Selain elemen yang sudah disertakan di atas, ini menunjukkan bagaimana memasukkan metode reguler dan statis:
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();
Secara umum, README ini tampaknya menjadi sumber yang bagus untuk mendekati tipe dengan luwak.
sumber
IUserDocument
ke dalamUserSchema
, yang menciptakan risiko pemeliharaan karena model menjadi lebih kompleks. Paket sukats-mongoose
dantypegoose
mencoba memecahkan masalah itu, meskipun diakui dengan sedikit kesalahan sintaksis.Jika Anda ingin memastikan bahwa skema Anda memenuhi jenis model dan sebaliknya, solusi ini menawarkan pengetikan yang lebih baik daripada yang disarankan @bingles:
Jenis file yang umum:
ToSchema.ts
(Jangan panik! Cukup salin dan tempel)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>;
dan contoh model:
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);
sumber