Skip to content

Commit d287bae

Browse files
committed
fix: typing for mongodb 4.3.1
1 parent 3ca4686 commit d287bae

File tree

6 files changed

+71
-49
lines changed

6 files changed

+71
-49
lines changed

src/entity/manager.ts

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
MongoClient,
1919
ObjectId,
2020
TransactionOptions,
21-
UpdateOptions
21+
UpdateOptions,
22+
WithId
2223
} from 'mongodb';
2324

2425
import { DEBUG } from '../constants';
@@ -140,7 +141,11 @@ export class EntityManager {
140141
return this.getDatabase(databaseName).collection(this.getCollectionName(nameOrInstance));
141142
}
142143

143-
async validate(obj: any, validatorOptions: ValidatorOptions = {}, throwError: boolean = false) {
144+
async validate<Model extends EntityInterface = any>(
145+
obj: Model,
146+
validatorOptions: ValidatorOptions = {},
147+
throwError: boolean = false
148+
) {
144149
const errors = await validate(obj, {
145150
validationError: { target: true, value: true },
146151
whitelist: true,
@@ -155,12 +160,12 @@ export class EntityManager {
155160
}
156161

157162
async save<Model extends EntityInterface>(
158-
entity: Model,
163+
entity: Model | WithId<Model>,
159164
options: (InsertOneOptions | UpdateOptions) & {
160165
skipValidation?: boolean;
161166
validatorOptions?: ValidatorOptions;
162167
} = {}
163-
): Promise<Model> {
168+
): Promise<WithId<Model>> {
164169
const entityName = entity.constructor.name;
165170
const ctx = this.getSessionContext();
166171
try {
@@ -174,8 +179,8 @@ export class EntityManager {
174179
if (Model === undefined) {
175180
throw new Error(`Can not find model ${entityName}`);
176181
}
177-
const proxy = new Model();
178-
this.merge(proxy, entity);
182+
183+
const proxy = this.merge(new Model(), entity);
179184

180185
const operationOptions = {
181186
...(ctx !== undefined ? { session: ctx.session } : {}),
@@ -217,7 +222,7 @@ export class EntityManager {
217222
this.merge(entity, proxy);
218223

219224
this.log('%s %s saved', Model.name, entity._id?.toHexString());
220-
return entity;
225+
return entity as WithId<Model>;
221226
} catch (e) {
222227
this.log('error saving %s', entityName);
223228
throw e;
@@ -228,29 +233,29 @@ export class EntityManager {
228233
classType: ClassConstructor<Model>,
229234
query: Filter<Model>,
230235
options: FindOptions<Model> = {}
231-
): Promise<FindCursor<Model>> {
236+
): Promise<FindCursor<WithId<Model>>> {
232237
this.log('find %s %o', classType.name, query);
233238
const ctx = this.getSessionContext();
234239
const cursor = this.getCollection(classType).find(query, {
235240
...(ctx !== undefined ? { session: ctx.session } : {}),
236241
...options
237242
});
238-
return cursor.map((data) => this.fromPlain<Model>(classType, data));
243+
return cursor.map((data) => this.fromPlain(classType, data) as WithId<Model>);
239244
}
240245

241246
async findOne<Model extends EntityInterface>(
242247
classType: ClassConstructor<Model>,
243248
query: Filter<Model>,
244249
options: FindOptions = {}
245-
): Promise<Model | undefined> {
250+
): Promise<WithId<Model> | undefined> {
246251
this.log('findOne %s %o', classType.name, query, options);
247252
const ctx = this.getSessionContext();
248253
const obj = await this.getCollection(classType).findOne(query, {
249254
...(ctx !== undefined ? { session: ctx.session } : {}),
250255
...options
251256
});
252257
if (obj !== null) {
253-
return this.fromPlain<Model>(classType, obj);
258+
return this.fromPlain<Model>(classType, obj) as WithId<Model>;
254259
}
255260
}
256261

@@ -269,14 +274,17 @@ export class EntityManager {
269274
}
270275

271276
isIdQuery(query: any): boolean {
272-
return Object.keys(query).length === 1 && query._id;
277+
return Object.keys(query).length === 1 && query._id instanceof ObjectId;
273278
}
274279

275280
isIdsQuery(query: any): boolean {
276281
return this.isIdQuery(query) && Array.isArray(query._id.$in);
277282
}
278283

279-
protected async deleteCascade<Model extends EntityInterface>(classType: ClassConstructor<Model>, entity: Model) {
284+
protected async deleteCascade<Model extends EntityInterface>(
285+
classType: ClassConstructor<Model>,
286+
entity: WithId<Model>
287+
) {
280288
const relationshipsCascades = getRelationshipsCascadesMetadata(classType);
281289

282290
if (!Array.isArray(relationshipsCascades)) {
@@ -326,7 +334,7 @@ export class EntityManager {
326334

327335
async deleteMany<Model extends EntityInterface>(
328336
classType: ClassConstructor<Model>,
329-
query: any,
337+
query: Filter<Model>,
330338
options: DeleteOptions = {}
331339
) {
332340
this.log('deleteMany %s %o', classType.name, query);
@@ -355,7 +363,7 @@ export class EntityManager {
355363
) {
356364
this.log('watch %o', pipes);
357365
const ctx = this.getSessionContext();
358-
return this.getCollection(classType).watch<Model>(pipes, {
366+
return this.getCollection(classType).watch<WithId<Model>>(pipes, {
359367
...(ctx !== undefined ? { session: ctx.session } : {}),
360368
...options
361369
});
@@ -365,7 +373,7 @@ export class EntityManager {
365373
obj: any,
366374
property: string,
367375
options: FindOptions = {}
368-
): Promise<R | undefined> {
376+
): Promise<WithId<R> | undefined> {
369377
this.log('getRelationship %s on %s', property, obj);
370378
const relationMetadata = getRelationshipMetadata<R>(obj.constructor, property, this, obj);
371379
if (isEmpty(relationMetadata)) {
@@ -385,7 +393,7 @@ export class EntityManager {
385393
const id: ObjectId = obj[property];
386394
const filter: Filter<R> = {};
387395
filter._id = id;
388-
const relationship = await this.findOne<R>(relationMetadata.type, filter);
396+
const relationship = await this.findOne<R>(relationMetadata.type, filter, options);
389397

390398
return relationship;
391399
}
@@ -394,7 +402,7 @@ export class EntityManager {
394402
obj: any,
395403
property: string,
396404
options: FindOptions = {}
397-
): Promise<R[]> {
405+
): Promise<Array<WithId<R>>> {
398406
this.log('getRelationships %s on %s', property, obj);
399407

400408
const relationMetadata = getRelationshipMetadata<R>(obj.constructor, property, this);
@@ -457,14 +465,14 @@ export class EntityManager {
457465
classType: ClassConstructor<Model>,
458466
data: object,
459467
options?: ClassTransformOptions
460-
) {
468+
): Model {
461469
this.log('transform fromPlain %s', classType.name);
462-
return fromPlain(classType, data, options);
470+
return fromPlain<Model>(classType, data, options);
463471
}
464472

465-
merge<Model extends EntityInterface>(entity: Model, data: Model, options?: ClassTransformOptions) {
473+
merge<Model extends EntityInterface>(entity: Model, data: Model, excludePrefixes?: string[]) {
466474
this.log('%s transform merge', getObjectName(entity));
467-
return merge(entity, data, options);
475+
return merge(entity, data, excludePrefixes);
468476
}
469477

470478
async createIndexs<Model extends EntityInterface>(model: ClassConstructor<Model>) {

src/entity/repository.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ClassConstructor, ClassTransformOptions } from 'class-transformer';
22
import { ValidatorOptions } from 'class-validator';
3-
import { ChangeStreamOptions, CountDocumentsOptions, DeleteOptions, Document, Filter, FindOptions } from 'mongodb';
3+
import { ChangeStreamOptions, CountDocumentsOptions, DeleteOptions, Document, Filter, FindOptions, WithId } from 'mongodb';
44

55
import { EntityInterface } from './interfaces';
66
import { EntityManager } from './manager';
@@ -16,7 +16,7 @@ export class EntityRepository<Model extends EntityInterface> {
1616
return this.em;
1717
}
1818

19-
async save(entity: Model, ...args: any[]) {
19+
async save(entity: Model | WithId<Model>, ...args: any[]): Promise<WithId<Model>> {
2020
return await this.em.save(entity, ...args);
2121
}
2222

@@ -40,32 +40,32 @@ export class EntityRepository<Model extends EntityInterface> {
4040
return await this.em.deleteOne(this.classType, query, options);
4141
}
4242

43-
async deleteMany(query: any, ...args: any) {
43+
async deleteMany(query: Filter<Model>, ...args: any) {
4444
return await this.em.deleteMany(this.classType, query, ...args);
4545
}
4646

4747
async getRelationship<E extends EntityInterface>(
4848
object: Model,
4949
property: string,
5050
options: FindOptions = {}
51-
): Promise<E | undefined> {
51+
): Promise<WithId<E> | undefined> {
5252
return await this.em.getRelationship<E>(object, property, options);
5353
}
5454

5555
async getRelationships<E extends EntityInterface>(
5656
object: Model,
5757
property: string,
5858
options: FindOptions = {}
59-
): Promise<E[]> {
59+
): Promise<Array<WithId<E>>> {
6060
return await this.em.getRelationships<E>(object, property, options);
6161
}
6262

6363
fromPlain(data: object | object[], options?: ClassTransformOptions) {
6464
return this.em.fromPlain<Model>(this.classType, data, options);
6565
}
6666

67-
merge(entity: Model, data: Model, options?: ClassTransformOptions) {
68-
return this.em.merge<Model>(entity, data, options);
67+
merge(entity: Model | WithId<Model>, data: Model | WithId<Model>, excludePrefixes?: string[]) {
68+
return this.em.merge(entity, data, excludePrefixes);
6969
}
7070

7171
async validate(entity: Model, validatorOptions: ValidatorOptions = {}, throwError: boolean = false) {

src/entity/service.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Injectable, NotFoundException } from '@nestjs/common';
22
import { isEmpty } from 'class-validator';
33
import Debug from 'debug';
44
import { camelCase } from 'lodash';
5-
import { ChangeStreamDocument, Filter as MongoFilter, ObjectId } from 'mongodb';
5+
import { ChangeStreamDocument, Filter as MongoFilter, ObjectId, WithId } from 'mongodb';
66

77
import { DEBUG } from '../constants';
88
import { EventCallback } from '../event/event';
@@ -36,15 +36,15 @@ export abstract class EntityService<
3636
return this.repository;
3737
}
3838

39-
async create(data: any, save = false, ...rest: any[]): Promise<Model> {
39+
async create(data: any, save = false, ...rest: any[]): Promise<WithId<Model> | Model> {
4040
const item = this.repository.fromPlain(data);
4141
if (save) {
4242
return await this.repository.save(item, ...rest);
4343
}
4444
return item;
4545
}
4646

47-
async get(itemId: ObjectId, ...rest: any[]): Promise<Model> {
47+
async get(itemId: ObjectId, ...rest: any[]): Promise<WithId<Model>> {
4848
const filter: MongoFilter<Model> = {};
4949
filter._id = itemId;
5050
const item = await this.repository.findOne(filter, ...rest);
@@ -73,7 +73,7 @@ export abstract class EntityService<
7373
return res;
7474
}
7575

76-
async update(itemId: ObjectId, data: any, save = false, ...rest: any[]): Promise<Model> {
76+
async update(itemId: ObjectId, data: any, save = false, ...rest: any[]): Promise<WithId<Model> | Model> {
7777
const entity = await this.get(itemId);
7878
const item = this.repository.merge(entity, data);
7979
if (save) {
@@ -91,24 +91,27 @@ export abstract class EntityService<
9191
}
9292
}
9393

94-
subscribe(onData: EventCallback<Model>) {
94+
subscribe(onData: EventCallback<WithId<Model>>) {
9595
return this.repository
9696
.watch([], { fullDocument: 'updateLookup' })
97-
.on('change', (change: ChangeStreamDocument<Model>) => {
97+
.on('change', (change: ChangeStreamDocument<WithId<Model>>) => {
9898
this.onData(change, onData).catch((e) => {
9999
throw e;
100100
});
101101
});
102102
}
103103

104-
protected readonly onData = async (change: ChangeStreamDocument<Model>, onData: EventCallback<Model>) => {
104+
protected readonly onData = async (
105+
change: ChangeStreamDocument<WithId<Model>>,
106+
onData: EventCallback<WithId<Model>>
107+
) => {
105108
try {
106109
const em = this.repository.getEm();
107110
const classType = this.repository.getClassType();
108111
const eventName = camelCase(`on_${change.operationType}_${classType.name}`);
109112
this.log('Event:%s for %s:%s', eventName, classType.name, change.documentKey);
110113
if (change.fullDocument !== undefined) {
111-
change.fullDocument = em.fromPlain<Model>(classType, change.fullDocument);
114+
change.fullDocument = em.fromPlain(classType, change.fullDocument) as WithId<Model>;
112115
}
113116
onData(eventName, change);
114117
} catch (e) {

src/transformer/utils.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
import {
2-
ClassConstructor,
3-
classToClassFromExist,
4-
classToPlain,
5-
ClassTransformOptions,
6-
plainToClass,
7-
} from 'class-transformer';
1+
import { ClassConstructor, classToPlain, ClassTransformOptions, plainToClass } from 'class-transformer';
2+
import { mergeWith, unset } from 'lodash';
83

94
import { EntityInterface } from '../entity/interfaces';
105

@@ -25,9 +20,20 @@ export const fromPlain = <Model extends EntityInterface>(
2520
/**
2621
* @param entity The model to hydrate
2722
* @param data The data to merge into the entity
23+
* @param excludePrefixes The prefixes to excludes (if the source contains key ith prefix)
2824
*/
29-
export const merge = <Model extends EntityInterface>(entity: Model, data: Model, options?: ClassTransformOptions) =>
30-
classToClassFromExist(data, entity, {
31-
excludePrefixes: EXCLUDED_PREFIXES,
32-
...options
25+
export const merge = <Model extends EntityInterface>(entity: Model, data: Model, excludePrefixes?: string[]) => {
26+
mergeWith(entity, data, (value: any, srcValue: any) => {
27+
return srcValue ?? value;
3328
});
29+
30+
// clean any excluded property
31+
for (const prop in entity) {
32+
if (excludePrefixes?.some((p) => prop.startsWith(p)) === true) {
33+
unset(entity, prop);
34+
// return undefined;
35+
}
36+
}
37+
38+
return entity;
39+
};

test/relationship/entity.relationship.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ export class EntityRelationship extends Entity {
3232
isArray: true
3333
})
3434
childrenAsReference?: ObjectId[];
35+
36+
__shouldBeExcluded?: string;
3537
}

test/transformers/utils.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ describe('merge', () => {
5656
entity1._id = id;
5757
entity1.property = 'test';
5858
entity1.children = [id2, id3];
59+
entity1.__shouldBeExcluded = 'shouldbeexcluded';
5960

6061
const entity2 = new EntityRelationship();
6162
entity2.property = 'bad';
63+
entity2.__shouldBeExcluded = 'shouldbeexcluded';
6264

63-
merge(entity2, entity1);
65+
merge(entity2, entity1, ['__']);
6466

6567
expect(entity1._id.equals(id)).toBe(true);
6668
expect(entity1).toHaveProperty('property', 'test');
@@ -80,5 +82,6 @@ describe('merge', () => {
8082
}
8183
expect(entity2.children[0].equals(id2)).toBe(true);
8284
expect(entity2.children[1].equals(id3)).toBe(true);
85+
expect(entity2).toHaveProperty('__shouldBeExcluded', undefined);
8386
});
8487
});

0 commit comments

Comments
 (0)