Skip to content

Instantly share code, notes, and snippets.

@nartc
Last active November 16, 2021 16:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nartc/bbee6424eb7dc379ba7915c0db583a2e to your computer and use it in GitHub Desktop.
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;
}
}
@devlegacy
Copy link

How can contributed?

@geniusmonir
Copy link

There are two errors shown in this file
1

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

Error 2
2

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

@example

class ClassName {}
const NameModel = getModelForClass(ClassName);`

How to resolve this type @nartc ?

@geniusmonir
Copy link

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.

@nartc
Copy link
Author

nartc commented Oct 24, 2021

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

@devlegacy
Copy link

devlegacy commented Nov 16, 2021

For mongoose": "^6.0.13" i have an issue with EnforceDocument

Solved with:

Replace EnforceDocument by HydratedDocument

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment