-
-
Save nartc/bbee6424eb7dc379ba7915c0db583a2e to your computer and use it in GitHub Desktop.
import { InternalServerErrorException } from '@nestjs/common'; | |
import type { DocumentType, ReturnModelType } from '@typegoose/typegoose'; | |
import type { MongoError } from 'mongodb'; | |
import { | |
EnforceDocument, | |
FilterQuery, | |
QueryOptions as MongooseQueryOptions, | |
QueryWithHelpers, | |
Types, | |
UpdateQuery | |
} from 'mongoose'; | |
import { BaseModel } from './base.model'; | |
interface QueryOptions { | |
lean?: boolean; | |
autopopulate?: boolean; | |
} | |
export type EnforceDocumentType<TModel extends BaseModel> = EnforceDocument< | |
DocumentType<TModel>, | |
Record<string, unknown>, | |
Record<string, unknown> | |
>; | |
export type QueryList<TModel extends BaseModel> = QueryWithHelpers< | |
Array<EnforceDocumentType<TModel>>, | |
EnforceDocumentType<TModel> | |
>; | |
export type QueryItem< | |
TModel extends BaseModel, | |
TReturnType = EnforceDocumentType<TModel> | null | |
> = QueryWithHelpers<TReturnType, EnforceDocumentType<TModel>>; | |
export type ModelType<TModel extends BaseModel> = ReturnModelType< | |
new (...args: unknown[]) => TModel | |
>; | |
export abstract class BaseService<TModel extends BaseModel> { | |
protected constructor(protected model: ModelType<TModel>) {} | |
private static get defaultOptions(): QueryOptions { | |
return { lean: true, autopopulate: true }; | |
} | |
private static getQueryOptions(options?: QueryOptions) { | |
const mergedOptions = { | |
...BaseService.defaultOptions, | |
...(options || {}), | |
}; | |
const option: { virtuals: boolean; autopopulate?: boolean } | null = | |
mergedOptions.lean ? { virtuals: true } : null; | |
if (option && mergedOptions.autopopulate) { | |
option['autopopulate'] = true; | |
} | |
return { lean: option, autopopulate: mergedOptions.autopopulate }; | |
} | |
protected static throwMongoError(err: MongoError): void { | |
throw new InternalServerErrorException(err, err.errmsg); | |
} | |
protected toObjectId(id: string) { | |
return new Types.ObjectId(id); | |
} | |
createModel(doc?: Partial<TModel>): TModel { | |
return new this.model(doc); | |
} | |
findAll(options?: QueryOptions): QueryList<TModel> { | |
return this.model.find().setOptions(BaseService.getQueryOptions(options)); | |
} | |
findOne(options?: QueryOptions): QueryItem<TModel> { | |
return this.model | |
.findOne() | |
.setOptions(BaseService.getQueryOptions(options)); | |
} | |
findById(id: string, options?: QueryOptions): QueryItem<TModel> { | |
return this.model | |
.findById(this.toObjectId(id)) | |
.setOptions(BaseService.getQueryOptions(options)); | |
} | |
async create(item: TModel): Promise<DocumentType<TModel> | null> { | |
try { | |
return await this.model.create(item); | |
} catch (e) { | |
BaseService.throwMongoError(e); | |
} | |
return null; | |
} | |
deleteOne(options?: QueryOptions): QueryItem<TModel> { | |
return this.model | |
.findOneAndDelete() | |
.setOptions(BaseService.getQueryOptions(options)); | |
} | |
deleteById(id: string, options?: QueryOptions): QueryItem<TModel> { | |
return this.model | |
.findByIdAndDelete(this.toObjectId(id)) | |
.setOptions(BaseService.getQueryOptions(options)); | |
} | |
update(item: TModel, options?: QueryOptions): QueryItem<TModel> { | |
return this.model | |
.findByIdAndUpdate( | |
this.toObjectId(item.id), | |
{ $set: item } as UpdateQuery<DocumentType<TModel>>, | |
{ upsert: true, new: true } | |
) | |
.setOptions(BaseService.getQueryOptions(options)); | |
} | |
updateById( | |
id: string, | |
updateQuery: UpdateQuery<DocumentType<TModel>>, | |
updateOptions: MongooseQueryOptions & { multi?: boolean } = {}, | |
options?: QueryOptions | |
): QueryItem<TModel> { | |
return this.updateByFilter( | |
{ _id: this.toObjectId(id) } as FilterQuery<DocumentType<TModel>>, | |
updateQuery, | |
updateOptions, | |
options | |
); | |
} | |
updateByFilter( | |
filter: FilterQuery<DocumentType<TModel>> = {}, | |
updateQuery: UpdateQuery<DocumentType<TModel>>, | |
updateOptions: Omit<MongooseQueryOptions, 'new'> = {}, | |
options?: QueryOptions | |
): QueryItem<TModel> { | |
return this.model | |
.findOneAndUpdate( | |
filter, | |
updateQuery, | |
Object.assign({ new: true }, updateOptions) | |
) | |
.setOptions(BaseService.getQueryOptions(options)); | |
} | |
count( | |
filter: FilterQuery<DocumentType<TModel>> = {} | |
): QueryItem<TModel, number> { | |
return this.model.count(filter); | |
} | |
async countAsync( | |
filter: FilterQuery<DocumentType<TModel>> = {} | |
): Promise<number> { | |
try { | |
return await this.count(filter).exec(); | |
} catch (e) { | |
BaseService.throwMongoError(e); | |
} | |
return 0; | |
} | |
async exists( | |
filter: FilterQuery<DocumentType<TModel>> = {} | |
): Promise<boolean> { | |
try { | |
return await this.model.exists(filter); | |
} catch (e) { | |
BaseService.throwMongoError(e); | |
} | |
return false; | |
} | |
} |
There are two errors shown in this file
On Hover error details:
No overload matches this call. The last overload gave the following error. Argument of type 'UpdateWithAggregationPipeline' is not assignable to parameter of type 'UpdateQuery<DocumentType<TModel, BeAnObject>>'. Type 'UpdateAggregationStage[]' is not assignable to type 'ReadonlyPartial<_UpdateQueryDef<LeanDocument<DocumentType<TModel, BeAnObject>>>>'.ts(2769) index.d.ts(965, 5): The last overload is declared here. (parameter) item: TModel extends BaseModel
index.d.ts(965, 5
findByIdAndUpdate(id: mongodb.ObjectId | any, update: UpdateQuery<T>, callback: (err: CallbackError, doc: EnforceDocument<T, TMethods, TVirtuals> | null, res: any) => void): QueryWithHelpers<EnforceDocument<T, TMethods, TVirtuals> | null, EnforceDocument<T, TMethods, TVirtuals>, TQueryHelpers, T>;
On Hover:
`Generic type 'EnforceDocument' requires 3 type argument(s).ts(2314)
(alias) type DocumentType<T, QueryHelpers = BeAnObject> = (T extends {
_id: unknown;
} ? mongoose.Document<T["_id"], QueryHelpers, any> & T : mongoose.Document<any, QueryHelpers, any> & T) & IObjectWithTypegooseFunction
import DocumentType
Get the Type of an instance of a Document with Class properties
class ClassName {}
const NameModel = getModelForClass(ClassName);`
How to resolve this type @nartc ?
I have rechecked your last revision @nartc . Its still persist the squiggled line under both lines. Please fix the issue as i wanna integrate it to my big project. Thanks a lot for your time.
@geniusmonir I have updated the gist with what I currently use in a project. Though it is working, but because of mongoose's EnforceDocument
type, when you invoke the method and call exec()
, you'd still have to cast to the correct model.
getUser() {
// though we know for a fact that await ...exec() will return a Promise<User>, but mongoose's EnforceDocument just screws everything up
return await this.findOne().exec() as User;
}
For mongoose": "^6.0.13"
i have an issue with EnforceDocument
Solved with:
Replace EnforceDocument
by HydratedDocument
How can contributed?