• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.49
/src/entity-manager/MongoEntityManager.ts
1
import { EntityManager } from "./EntityManager"
4✔
2
import { EntityTarget } from "../common/EntityTarget"
3

4
import { ObjectLiteral } from "../common/ObjectLiteral"
5
import { MongoQueryRunner } from "../driver/mongodb/MongoQueryRunner"
6
import { MongoDriver } from "../driver/mongodb/MongoDriver"
7
import { DocumentToEntityTransformer } from "../query-builder/transformer/DocumentToEntityTransformer"
4✔
8
import { FindManyOptions } from "../find-options/FindManyOptions"
9
import { FindOptionsUtils } from "../find-options/FindOptionsUtils"
4✔
10
import { PlatformTools } from "../platform/PlatformTools"
4✔
11
import { QueryDeepPartialEntity } from "../query-builder/QueryPartialEntity"
12
import { InsertResult } from "../query-builder/result/InsertResult"
4✔
13
import { UpdateResult } from "../query-builder/result/UpdateResult"
4✔
14
import { DeleteResult } from "../query-builder/result/DeleteResult"
4✔
15
import { EntityMetadata } from "../metadata/EntityMetadata"
16

17
import {
18
    BulkWriteResult,
19
    AggregationCursor,
20
    Collection,
21
    FindCursor,
22
    Document,
23
    AggregateOptions,
24
    AnyBulkWriteOperation,
25
    BulkWriteOptions,
26
    Filter,
27
    CountOptions,
28
    IndexSpecification,
29
    CreateIndexesOptions,
30
    IndexDescription,
31
    DeleteResult as DeleteResultMongoDb,
32
    DeleteOptions,
33
    CommandOperationOptions,
34
    FindOneAndDeleteOptions,
35
    FindOneAndReplaceOptions,
36
    UpdateFilter,
37
    FindOneAndUpdateOptions,
38
    RenameOptions,
39
    ReplaceOptions,
40
    UpdateResult as UpdateResultMongoDb,
41
    CollStats,
42
    CollStatsOptions,
43
    ChangeStreamOptions,
44
    ChangeStream,
45
    UpdateOptions,
46
    ListIndexesOptions,
47
    ListIndexesCursor,
48
    OptionalId,
49
    InsertOneOptions,
50
    InsertOneResult,
51
    InsertManyResult,
52
    UnorderedBulkOperation,
53
    OrderedBulkOperation,
54
    IndexInformationOptions,
55
    ObjectId,
56
    FilterOperators,
57
    CountDocumentsOptions,
58
} from "../driver/mongodb/typings"
59
import { DataSource } from "../data-source/DataSource"
60
import { MongoFindManyOptions } from "../find-options/mongodb/MongoFindManyOptions"
61
import { MongoFindOneOptions } from "../find-options/mongodb/MongoFindOneOptions"
62
import {
63
    FindOptionsSelect,
64
    FindOptionsSelectByString,
65
} from "../find-options/FindOptionsSelect"
66
import { ObjectUtils } from "../util/ObjectUtils"
4✔
67
import { ColumnMetadata } from "../metadata/ColumnMetadata"
68

69
/**
70
 * Entity manager supposed to work with any entity, automatically find its repository and call its methods,
71
 * whatever entity type are you passing.
72
 *
73
 * This implementation is used for MongoDB driver which has some specifics in its EntityManager.
74
 */
75
export class MongoEntityManager extends EntityManager {
4✔
76
    readonly "@instanceof" = Symbol.for("MongoEntityManager")
×
77

78
    get mongoQueryRunner(): MongoQueryRunner {
79
        return (this.connection.driver as MongoDriver)
×
80
            .queryRunner as MongoQueryRunner
81
    }
82

83
    // -------------------------------------------------------------------------
84
    // Constructor
85
    // -------------------------------------------------------------------------
86

87
    constructor(connection: DataSource) {
88
        super(connection)
×
89
    }
90

91
    // -------------------------------------------------------------------------
92
    // Overridden Methods
93
    // -------------------------------------------------------------------------
94

95
    /**
96
     * Finds entities that match given find options.
97
     */
98
    /**
99
     * Finds entities that match given find options or conditions.
100
     */
101
    async find<Entity>(
102
        entityClassOrName: EntityTarget<Entity>,
103
        optionsOrConditions?:
104
            | FindManyOptions<Entity>
105
            | Partial<Entity>
106
            | FilterOperators<Entity>,
107
    ): Promise<Entity[]> {
108
        const query =
109
            this.convertFindManyOptionsOrConditionsToMongodbQuery(
×
110
                optionsOrConditions,
111
            )
112
        const cursor = this.createEntityCursor<Entity>(
×
113
            entityClassOrName,
114
            query as Filter<Entity>,
115
        )
116
        const deleteDateColumn =
117
            this.connection.getMetadata(entityClassOrName).deleteDateColumn
×
118
        if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
×
119
            if (optionsOrConditions.select)
×
120
                cursor.project(
×
121
                    this.convertFindOptionsSelectToProjectCriteria(
122
                        optionsOrConditions.select,
123
                    ),
124
                )
125
            if (optionsOrConditions.skip) cursor.skip(optionsOrConditions.skip)
×
126
            if (optionsOrConditions.take) cursor.limit(optionsOrConditions.take)
×
127
            if (optionsOrConditions.order)
×
128
                cursor.sort(
×
129
                    this.convertFindOptionsOrderToOrderCriteria(
130
                        optionsOrConditions.order,
131
                    ),
132
                )
133
            if (deleteDateColumn && !optionsOrConditions.withDeleted) {
×
134
                this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
135
            }
136
        } else if (deleteDateColumn) {
×
137
            this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
138
        }
139
        return cursor.toArray()
×
140
    }
141

142
    /**
143
     * Finds entities that match given find options or conditions.
144
     * Also counts all entities that match given conditions,
145
     * but ignores pagination settings (from and take options).
146
     */
147
    async findAndCount<Entity>(
148
        entityClassOrName: EntityTarget<Entity>,
149
        options?: MongoFindManyOptions<Entity>,
150
    ): Promise<[Entity[], number]> {
151
        return this.executeFindAndCount(entityClassOrName, options)
×
152
    }
153

154
    /**
155
     * Finds entities that match given where conditions.
156
     */
157
    async findAndCountBy<Entity>(
158
        entityClassOrName: EntityTarget<Entity>,
159
        where: any,
160
    ): Promise<[Entity[], number]> {
161
        return this.executeFindAndCount(entityClassOrName, where)
×
162
    }
163

164
    /**
165
     * Finds entities by ids.
166
     * Optionally find options can be applied.
167
     *
168
     * @deprecated use `findBy` method instead.
169
     */
170
    async findByIds<Entity>(
171
        entityClassOrName: EntityTarget<Entity>,
172
        ids: any[],
173
        optionsOrConditions?: FindManyOptions<Entity> | Partial<Entity>,
174
    ): Promise<Entity[]> {
175
        const metadata = this.connection.getMetadata(entityClassOrName)
×
176
        const query =
177
            this.convertFindManyOptionsOrConditionsToMongodbQuery(
×
178
                optionsOrConditions,
179
            ) || {}
180
        const objectIdInstance = PlatformTools.load("mongodb").ObjectId
×
181
        query["_id"] = {
×
182
            $in: ids.map((id) => {
183
                if (typeof id === "string") {
×
184
                    return new objectIdInstance(id)
×
185
                }
186

187
                if (typeof id === "object") {
×
188
                    if (id instanceof objectIdInstance) {
×
189
                        return id
×
190
                    }
191

192
                    const propertyName = metadata.objectIdColumn!.propertyName
×
193

194
                    if (id[propertyName] instanceof objectIdInstance) {
×
195
                        return id[propertyName]
×
196
                    }
197
                }
198
            }),
199
        }
200

201
        const cursor = this.createEntityCursor<Entity>(
×
202
            entityClassOrName,
203
            query as Filter<Entity>,
204
        )
205
        if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
×
206
            if (optionsOrConditions.select)
×
207
                cursor.project(
×
208
                    this.convertFindOptionsSelectToProjectCriteria(
209
                        optionsOrConditions.select,
210
                    ),
211
                )
212
            if (optionsOrConditions.skip) cursor.skip(optionsOrConditions.skip)
×
213
            if (optionsOrConditions.take) cursor.limit(optionsOrConditions.take)
×
214
            if (optionsOrConditions.order)
×
215
                cursor.sort(
×
216
                    this.convertFindOptionsOrderToOrderCriteria(
217
                        optionsOrConditions.order,
218
                    ),
219
                )
220
        }
221
        return cursor.toArray()
×
222
    }
223

224
    /**
225
     * Finds first entity that matches given conditions and/or find options.
226
     */
227
    async findOne<Entity>(
228
        entityClassOrName: EntityTarget<Entity>,
229
        options: MongoFindOneOptions<Entity>,
230
    ): Promise<Entity | null> {
231
        return this.executeFindOne(entityClassOrName, options)
×
232
    }
233

234
    /**
235
     * Finds first entity that matches given WHERE conditions.
236
     */
237
    async findOneBy<Entity>(
238
        entityClassOrName: EntityTarget<Entity>,
239
        where: any,
240
    ): Promise<Entity | null> {
241
        return this.executeFindOne(entityClassOrName, where)
×
242
    }
243

244
    /**
245
     * Finds entity that matches given id.
246
     *
247
     * @deprecated use `findOneBy` method instead in conjunction with `In` operator, for example:
248
     *
249
     * .findOneBy({
250
     *     id: 1 // where "id" is your primary column name
251
     * })
252
     */
253
    async findOneById<Entity>(
254
        entityClassOrName: EntityTarget<Entity>,
255
        id: string | number | Date | ObjectId,
256
    ): Promise<Entity | null> {
257
        return this.executeFindOne(entityClassOrName, id)
×
258
    }
259

260
    /**
261
     * Inserts a given entity into the database.
262
     * Unlike save method executes a primitive operation without cascades, relations and other operations included.
263
     * Executes fast and efficient INSERT query.
264
     * Does not check if entity exist in the database, so query will fail if duplicate entity is being inserted.
265
     * You can execute bulk inserts using this method.
266
     */
267
    async insert<Entity>(
268
        target: EntityTarget<Entity>,
269
        entity:
270
            | QueryDeepPartialEntity<Entity>
271
            | QueryDeepPartialEntity<Entity>[],
272
    ): Promise<InsertResult> {
273
        // todo: convert entity to its database name
274
        const result = new InsertResult()
×
275
        if (Array.isArray(entity)) {
×
276
            result.raw = await this.insertMany(target, entity)
×
277
            Object.keys(result.raw.insertedIds).forEach((key: any) => {
×
278
                const insertedId = result.raw.insertedIds[key]
×
279
                result.generatedMaps.push(
×
280
                    this.connection.driver.createGeneratedMap(
281
                        this.connection.getMetadata(target),
282
                        insertedId,
283
                    )!,
284
                )
285
                result.identifiers.push(
×
286
                    this.connection.driver.createGeneratedMap(
287
                        this.connection.getMetadata(target),
288
                        insertedId,
289
                    )!,
290
                )
291
            })
292
        } else {
293
            result.raw = await this.insertOne(target, entity)
×
294
            result.generatedMaps.push(
×
295
                this.connection.driver.createGeneratedMap(
296
                    this.connection.getMetadata(target),
297
                    result.raw.insertedId,
298
                )!,
299
            )
300
            result.identifiers.push(
×
301
                this.connection.driver.createGeneratedMap(
302
                    this.connection.getMetadata(target),
303
                    result.raw.insertedId,
304
                )!,
305
            )
306
        }
307

308
        return result
×
309
    }
310

311
    /**
312
     * Updates entity partially. Entity can be found by a given conditions.
313
     * Unlike save method executes a primitive operation without cascades, relations and other operations included.
314
     * Executes fast and efficient UPDATE query.
315
     * Does not check if entity exist in the database.
316
     */
317
    async update<Entity>(
318
        target: EntityTarget<Entity>,
319
        criteria:
320
            | string
321
            | string[]
322
            | number
323
            | number[]
324
            | Date
325
            | Date[]
326
            | ObjectId
327
            | ObjectId[]
328
            | ObjectLiteral,
329
        partialEntity: QueryDeepPartialEntity<Entity>,
330
    ): Promise<UpdateResult> {
331
        const result = new UpdateResult()
×
332

333
        if (Array.isArray(criteria)) {
×
334
            const updateResults = await Promise.all(
×
335
                (criteria as any[]).map((criteriaItem) => {
336
                    return this.update(target, criteriaItem, partialEntity)
×
337
                }),
338
            )
339

340
            result.raw = updateResults.map((r) => r.raw)
×
341
            result.affected = updateResults
×
342
                .map((r) => r.affected || 0)
×
343
                .reduce((c, r) => c + r, 0)
×
344
            result.generatedMaps = updateResults.reduce(
×
345
                (c, r) => c.concat(r.generatedMaps),
×
346
                [] as ObjectLiteral[],
347
            )
348
        } else {
349
            const metadata = this.connection.getMetadata(target)
×
350
            const mongoResult = await this.updateMany(
×
351
                target,
352
                this.convertMixedCriteria(metadata, criteria),
353
                { $set: partialEntity },
354
            )
355

356
            result.raw = mongoResult
×
357
            result.affected = mongoResult.modifiedCount
×
358
        }
359

360
        return result
×
361
    }
362

363
    /**
364
     * Deletes entities by a given conditions.
365
     * Unlike save method executes a primitive operation without cascades, relations and other operations included.
366
     * Executes fast and efficient DELETE query.
367
     * Does not check if entity exist in the database.
368
     */
369
    async delete<Entity>(
370
        target: EntityTarget<Entity>,
371
        criteria:
372
            | string
373
            | string[]
374
            | number
375
            | number[]
376
            | Date
377
            | Date[]
378
            | ObjectId
379
            | ObjectId[]
380
            | ObjectLiteral[],
381
    ): Promise<DeleteResult> {
382
        const result = new DeleteResult()
×
383

384
        if (Array.isArray(criteria)) {
×
385
            const deleteResults = await Promise.all(
×
386
                (criteria as any[]).map((criteriaItem) => {
387
                    return this.delete(target, criteriaItem)
×
388
                }),
389
            )
390

391
            result.raw = deleteResults.map((r) => r.raw)
×
392
            result.affected = deleteResults
×
393
                .map((r) => r.affected || 0)
×
394
                .reduce((c, r) => c + r, 0)
×
395
        } else {
396
            const mongoResult = await this.deleteMany(
×
397
                target,
398
                this.convertMixedCriteria(
399
                    this.connection.getMetadata(target),
400
                    criteria,
401
                ),
402
            )
403

404
            result.raw = mongoResult
×
405
            result.affected = mongoResult.deletedCount
×
406
        }
407

408
        return result
×
409
    }
410

411
    // -------------------------------------------------------------------------
412
    // Public Methods
413
    // -------------------------------------------------------------------------
414

415
    /**
416
     * Creates a cursor for a query that can be used to iterate over results from MongoDB.
417
     */
418
    createCursor<Entity, T = any>(
419
        entityClassOrName: EntityTarget<Entity>,
420
        query: ObjectLiteral = {},
×
421
    ): FindCursor<T> {
422
        const metadata = this.connection.getMetadata(entityClassOrName)
×
423
        return this.mongoQueryRunner.cursor(metadata.tableName, query)
×
424
    }
425

426
    /**
427
     * Creates a cursor for a query that can be used to iterate over results from MongoDB.
428
     * This returns modified version of cursor that transforms each result into Entity model.
429
     */
430
    createEntityCursor<Entity>(
431
        entityClassOrName: EntityTarget<Entity>,
432
        query: ObjectLiteral = {},
×
433
    ): FindCursor<Entity> {
434
        const metadata = this.connection.getMetadata(entityClassOrName)
×
435
        const cursor = this.createCursor(entityClassOrName, query)
×
436
        this.applyEntityTransformationToCursor(metadata, cursor)
×
437
        return cursor
×
438
    }
439

440
    /**
441
     * Execute an aggregation framework pipeline against the collection.
442
     */
443
    aggregate<Entity, R = any>(
444
        entityClassOrName: EntityTarget<Entity>,
445
        pipeline: Document[],
446
        options?: AggregateOptions,
447
    ): AggregationCursor<R> {
448
        const metadata = this.connection.getMetadata(entityClassOrName)
×
449
        return this.mongoQueryRunner.aggregate(
×
450
            metadata.tableName,
451
            pipeline,
452
            options,
453
        )
454
    }
455

456
    /**
457
     * Execute an aggregation framework pipeline against the collection.
458
     * This returns modified version of cursor that transforms each result into Entity model.
459
     */
460
    aggregateEntity<Entity>(
461
        entityClassOrName: EntityTarget<Entity>,
462
        pipeline: Document[],
463
        options?: AggregateOptions,
464
    ): AggregationCursor<Entity> {
465
        const metadata = this.connection.getMetadata(entityClassOrName)
×
466
        const cursor = this.mongoQueryRunner.aggregate(
×
467
            metadata.tableName,
468
            pipeline,
469
            options,
470
        )
471
        this.applyEntityTransformationToCursor(metadata, cursor)
×
472
        return cursor
×
473
    }
474

475
    /**
476
     * Perform a bulkWrite operation without a fluent API.
477
     */
478
    bulkWrite<Entity>(
479
        entityClassOrName: EntityTarget<Entity>,
480
        operations: AnyBulkWriteOperation<Document>[],
481
        options?: BulkWriteOptions,
482
    ): Promise<BulkWriteResult> {
483
        const metadata = this.connection.getMetadata(entityClassOrName)
×
484
        return this.mongoQueryRunner.bulkWrite(
×
485
            metadata.tableName,
486
            operations,
487
            options,
488
        )
489
    }
490

491
    /**
492
     * Count number of matching documents in the db to a query.
493
     */
494
    count<Entity>(
495
        entityClassOrName: EntityTarget<Entity>,
496
        query: Filter<Document> = {},
×
497
        options: CountOptions = {},
×
498
    ): Promise<number> {
499
        const metadata = this.connection.getMetadata(entityClassOrName)
×
500
        return this.mongoQueryRunner.count(metadata.tableName, query, options)
×
501
    }
502

503
    /**
504
     * Count number of matching documents in the db to a query.
505
     */
506
    countDocuments<Entity>(
507
        entityClassOrName: EntityTarget<Entity>,
508
        query: Filter<Document> = {},
×
509
        options: CountDocumentsOptions = {},
×
510
    ): Promise<number> {
511
        const metadata = this.connection.getMetadata(entityClassOrName)
×
512
        return this.mongoQueryRunner.countDocuments(
×
513
            metadata.tableName,
514
            query,
515
            options,
516
        )
517
    }
518

519
    /**
520
     * Count number of matching documents in the db to a query.
521
     */
522
    countBy<Entity>(
523
        entityClassOrName: EntityTarget<Entity>,
524
        query?: ObjectLiteral,
525
        options?: CountOptions,
526
    ): Promise<number> {
527
        return this.count(entityClassOrName, query, options)
×
528
    }
529

530
    /**
531
     * Creates an index on the db and collection.
532
     */
533
    createCollectionIndex<Entity>(
534
        entityClassOrName: EntityTarget<Entity>,
535
        fieldOrSpec: IndexSpecification,
536
        options?: CreateIndexesOptions,
537
    ): Promise<string> {
538
        const metadata = this.connection.getMetadata(entityClassOrName)
×
539
        return this.mongoQueryRunner.createCollectionIndex(
×
540
            metadata.tableName,
541
            fieldOrSpec,
542
            options,
543
        )
544
    }
545

546
    /**
547
     * Creates multiple indexes in the collection, this method is only supported for MongoDB 2.6 or higher.
548
     * Earlier version of MongoDB will throw a command not supported error.
549
     * Index specifications are defined at http://docs.mongodb.org/manual/reference/command/createIndexes/.
550
     */
551
    createCollectionIndexes<Entity>(
552
        entityClassOrName: EntityTarget<Entity>,
553
        indexSpecs: IndexDescription[],
554
    ): Promise<string[]> {
555
        const metadata = this.connection.getMetadata(entityClassOrName)
×
556
        return this.mongoQueryRunner.createCollectionIndexes(
×
557
            metadata.tableName,
558
            indexSpecs,
559
        )
560
    }
561

562
    /**
563
     * Delete multiple documents on MongoDB.
564
     */
565
    deleteMany<Entity>(
566
        entityClassOrName: EntityTarget<Entity>,
567
        query: Filter<Document>,
568
        options: DeleteOptions = {},
×
569
    ): Promise<DeleteResultMongoDb> {
570
        const metadata = this.connection.getMetadata(entityClassOrName)
×
571
        return this.mongoQueryRunner.deleteMany(
×
572
            metadata.tableName,
573
            query,
574
            options,
575
        )
576
    }
577

578
    /**
579
     * Delete a document on MongoDB.
580
     */
581
    deleteOne<Entity>(
582
        entityClassOrName: EntityTarget<Entity>,
583
        query: Filter<Document>,
584
        options: DeleteOptions = {},
×
585
    ): Promise<DeleteResultMongoDb> {
586
        const metadata = this.connection.getMetadata(entityClassOrName)
×
587
        return this.mongoQueryRunner.deleteOne(
×
588
            metadata.tableName,
589
            query,
590
            options,
591
        )
592
    }
593

594
    /**
595
     * The distinct command returns returns a list of distinct values for the given key across a collection.
596
     */
597
    distinct<Entity>(
598
        entityClassOrName: EntityTarget<Entity>,
599
        key: string,
600
        query: Filter<Document>,
601
        options?: CommandOperationOptions,
602
    ): Promise<any> {
603
        const metadata = this.connection.getMetadata(entityClassOrName)
×
604
        return this.mongoQueryRunner.distinct(
×
605
            metadata.tableName,
606
            key,
607
            query,
608
            options,
609
        )
610
    }
611

612
    /**
613
     * Drops an index from this collection.
614
     */
615
    dropCollectionIndex<Entity>(
616
        entityClassOrName: EntityTarget<Entity>,
617
        indexName: string,
618
        options?: CommandOperationOptions,
619
    ): Promise<any> {
620
        const metadata = this.connection.getMetadata(entityClassOrName)
×
621
        return this.mongoQueryRunner.dropCollectionIndex(
×
622
            metadata.tableName,
623
            indexName,
624
            options,
625
        )
626
    }
627

628
    /**
629
     * Drops all indexes from the collection.
630
     */
631
    dropCollectionIndexes<Entity>(
632
        entityClassOrName: EntityTarget<Entity>,
633
    ): Promise<any> {
634
        const metadata = this.connection.getMetadata(entityClassOrName)
×
635
        return this.mongoQueryRunner.dropCollectionIndexes(metadata.tableName)
×
636
    }
637

638
    /**
639
     * Find a document and delete it in one atomic operation, requires a write lock for the duration of the operation.
640
     */
641
    findOneAndDelete<Entity>(
642
        entityClassOrName: EntityTarget<Entity>,
643
        query: ObjectLiteral,
644
        options?: FindOneAndDeleteOptions,
645
    ): Promise<Document | null> {
646
        const metadata = this.connection.getMetadata(entityClassOrName)
×
647
        return this.mongoQueryRunner.findOneAndDelete(
×
648
            metadata.tableName,
649
            query,
650
            options,
651
        )
652
    }
653

654
    /**
655
     * Find a document and replace it in one atomic operation, requires a write lock for the duration of the operation.
656
     */
657
    findOneAndReplace<Entity>(
658
        entityClassOrName: EntityTarget<Entity>,
659
        query: Filter<Document>,
660
        replacement: Document,
661
        options?: FindOneAndReplaceOptions,
662
    ): Promise<Document | null> {
663
        const metadata = this.connection.getMetadata(entityClassOrName)
×
664
        return this.mongoQueryRunner.findOneAndReplace(
×
665
            metadata.tableName,
666
            query,
667
            replacement,
668
            options,
669
        )
670
    }
671

672
    /**
673
     * Find a document and update it in one atomic operation, requires a write lock for the duration of the operation.
674
     */
675
    findOneAndUpdate<Entity>(
676
        entityClassOrName: EntityTarget<Entity>,
677
        query: Filter<Document>,
678
        update: UpdateFilter<Document>,
679
        options?: FindOneAndUpdateOptions,
680
    ): Promise<Document | null> {
681
        const metadata = this.connection.getMetadata(entityClassOrName)
×
682
        return this.mongoQueryRunner.findOneAndUpdate(
×
683
            metadata.tableName,
684
            query,
685
            update,
686
            options,
687
        )
688
    }
689

690
    /**
691
     * Retrieve all the indexes on the collection.
692
     */
693
    collectionIndexes<Entity>(
694
        entityClassOrName: EntityTarget<Entity>,
695
    ): Promise<Document> {
696
        const metadata = this.connection.getMetadata(entityClassOrName)
×
697
        return this.mongoQueryRunner.collectionIndexes(metadata.tableName)
×
698
    }
699

700
    /**
701
     * Retrieve all the indexes on the collection.
702
     */
703
    collectionIndexExists<Entity>(
704
        entityClassOrName: EntityTarget<Entity>,
705
        indexes: string | string[],
706
    ): Promise<boolean> {
707
        const metadata = this.connection.getMetadata(entityClassOrName)
×
708
        return this.mongoQueryRunner.collectionIndexExists(
×
709
            metadata.tableName,
710
            indexes,
711
        )
712
    }
713

714
    /**
715
     * Retrieves this collections index info.
716
     */
717
    collectionIndexInformation<Entity>(
718
        entityClassOrName: EntityTarget<Entity>,
719
        options?: IndexInformationOptions,
720
    ): Promise<any> {
721
        const metadata = this.connection.getMetadata(entityClassOrName)
×
722
        return this.mongoQueryRunner.collectionIndexInformation(
×
723
            metadata.tableName,
724
            options,
725
        )
726
    }
727

728
    /**
729
     * Initiate an In order bulk write operation, operations will be serially executed in the order they are added, creating a new operation for each switch in types.
730
     */
731
    initializeOrderedBulkOp<Entity>(
732
        entityClassOrName: EntityTarget<Entity>,
733
        options?: BulkWriteOptions,
734
    ): OrderedBulkOperation {
735
        const metadata = this.connection.getMetadata(entityClassOrName)
×
736
        return this.mongoQueryRunner.initializeOrderedBulkOp(
×
737
            metadata.tableName,
738
            options,
739
        )
740
    }
741

742
    /**
743
     * Initiate a Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order.
744
     */
745
    initializeUnorderedBulkOp<Entity>(
746
        entityClassOrName: EntityTarget<Entity>,
747
        options?: BulkWriteOptions,
748
    ): UnorderedBulkOperation {
749
        const metadata = this.connection.getMetadata(entityClassOrName)
×
750
        return this.mongoQueryRunner.initializeUnorderedBulkOp(
×
751
            metadata.tableName,
752
            options,
753
        )
754
    }
755

756
    /**
757
     * Inserts an array of documents into MongoDB.
758
     */
759
    insertMany<Entity>(
760
        entityClassOrName: EntityTarget<Entity>,
761
        docs: OptionalId<Document>[],
762
        options?: BulkWriteOptions,
763
    ): Promise<InsertManyResult> {
764
        const metadata = this.connection.getMetadata(entityClassOrName)
×
765
        return this.mongoQueryRunner.insertMany(
×
766
            metadata.tableName,
767
            docs,
768
            options,
769
        )
770
    }
771

772
    /**
773
     * Inserts a single document into MongoDB.
774
     */
775
    insertOne<Entity>(
776
        entityClassOrName: EntityTarget<Entity>,
777
        doc: OptionalId<Document>,
778
        options?: InsertOneOptions,
779
    ): Promise<InsertOneResult> {
780
        const metadata = this.connection.getMetadata(entityClassOrName)
×
781
        return this.mongoQueryRunner.insertOne(metadata.tableName, doc, options)
×
782
    }
783

784
    /**
785
     * Returns if the collection is a capped collection.
786
     */
787
    isCapped<Entity>(entityClassOrName: EntityTarget<Entity>): Promise<any> {
788
        const metadata = this.connection.getMetadata(entityClassOrName)
×
789
        return this.mongoQueryRunner.isCapped(metadata.tableName)
×
790
    }
791

792
    /**
793
     * Get the list of all indexes information for the collection.
794
     */
795
    listCollectionIndexes<Entity>(
796
        entityClassOrName: EntityTarget<Entity>,
797
        options?: ListIndexesOptions,
798
    ): ListIndexesCursor {
799
        const metadata = this.connection.getMetadata(entityClassOrName)
×
800
        return this.mongoQueryRunner.listCollectionIndexes(
×
801
            metadata.tableName,
802
            options,
803
        )
804
    }
805

806
    /**
807
     * Reindex all indexes on the collection Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections.
808
     */
809
    rename<Entity>(
810
        entityClassOrName: EntityTarget<Entity>,
811
        newName: string,
812
        options?: RenameOptions,
813
    ): Promise<Collection<Document>> {
814
        const metadata = this.connection.getMetadata(entityClassOrName)
×
815
        return this.mongoQueryRunner.rename(
×
816
            metadata.tableName,
817
            newName,
818
            options,
819
        )
820
    }
821

822
    /**
823
     * Replace a document on MongoDB.
824
     */
825
    replaceOne<Entity>(
826
        entityClassOrName: EntityTarget<Entity>,
827
        query: Filter<Document>,
828
        doc: Document,
829
        options?: ReplaceOptions,
830
    ): Promise<Document | UpdateResultMongoDb> {
831
        const metadata = this.connection.getMetadata(entityClassOrName)
×
832
        return this.mongoQueryRunner.replaceOne(
×
833
            metadata.tableName,
834
            query,
835
            doc,
836
            options,
837
        )
838
    }
839

840
    /**
841
     * Get all the collection statistics.
842
     */
843
    stats<Entity>(
844
        entityClassOrName: EntityTarget<Entity>,
845
        options?: CollStatsOptions,
846
    ): Promise<CollStats> {
847
        const metadata = this.connection.getMetadata(entityClassOrName)
×
848
        return this.mongoQueryRunner.stats(metadata.tableName, options)
×
849
    }
850

851
    watch<Entity>(
852
        entityClassOrName: EntityTarget<Entity>,
853
        pipeline?: Document[],
854
        options?: ChangeStreamOptions,
855
    ): ChangeStream {
856
        const metadata = this.connection.getMetadata(entityClassOrName)
×
857
        return this.mongoQueryRunner.watch(
×
858
            metadata.tableName,
859
            pipeline,
860
            options,
861
        )
862
    }
863

864
    /**
865
     * Update multiple documents on MongoDB.
866
     */
867
    updateMany<Entity>(
868
        entityClassOrName: EntityTarget<Entity>,
869
        query: Filter<Document>,
870
        update: UpdateFilter<Document>,
871
        options?: UpdateOptions,
872
    ): Promise<Document | UpdateResultMongoDb> {
873
        const metadata = this.connection.getMetadata(entityClassOrName)
×
874
        return this.mongoQueryRunner.updateMany(
×
875
            metadata.tableName,
876
            query,
877
            update,
878
            options,
879
        )
880
    }
881

882
    /**
883
     * Update a single document on MongoDB.
884
     */
885
    updateOne<Entity>(
886
        entityClassOrName: EntityTarget<Entity>,
887
        query: Filter<Document>,
888
        update: UpdateFilter<Document>,
889
        options?: UpdateOptions,
890
    ): Promise<Document | UpdateResultMongoDb> {
891
        const metadata = this.connection.getMetadata(entityClassOrName)
×
892
        return this.mongoQueryRunner.updateOne(
×
893
            metadata.tableName,
894
            query,
895
            update,
896
            options,
897
        )
898
    }
899

900
    // -------------------------------------------------------------------------
901
    // Protected Methods
902
    // -------------------------------------------------------------------------
903

904
    /**
905
     * Converts FindManyOptions to mongodb query.
906
     */
907
    protected convertFindManyOptionsOrConditionsToMongodbQuery<Entity>(
908
        optionsOrConditions:
909
            | MongoFindManyOptions<Entity>
910
            | Partial<Entity>
911
            | FilterOperators<Entity>
912
            | any[]
913
            | undefined,
914
    ): ObjectLiteral | undefined {
915
        if (!optionsOrConditions) return undefined
×
916

917
        if (FindOptionsUtils.isFindManyOptions<Entity>(optionsOrConditions))
×
918
            // If where condition is passed as a string which contains sql we have to ignore
919
            // as mongo is not a sql database
920
            return typeof optionsOrConditions.where === "string"
×
921
                ? {}
922
                : optionsOrConditions.where
923

924
        return optionsOrConditions
×
925
    }
926

927
    /**
928
     * Converts FindOneOptions to mongodb query.
929
     */
930
    protected convertFindOneOptionsOrConditionsToMongodbQuery<Entity>(
931
        optionsOrConditions:
932
            | MongoFindOneOptions<Entity>
933
            | Partial<Entity>
934
            | undefined,
935
    ): ObjectLiteral | undefined {
936
        if (!optionsOrConditions) return undefined
×
937

938
        if (FindOptionsUtils.isFindOneOptions<Entity>(optionsOrConditions))
×
939
            // If where condition is passed as a string which contains sql we have to ignore
940
            // as mongo is not a sql database
941
            return typeof optionsOrConditions.where === "string"
×
942
                ? {}
943
                : optionsOrConditions.where
944

945
        return optionsOrConditions
×
946
    }
947

948
    /**
949
     * Converts FindOptions into mongodb order by criteria.
950
     */
951
    protected convertFindOptionsOrderToOrderCriteria(order: ObjectLiteral) {
952
        return Object.keys(order).reduce((orderCriteria, key) => {
×
953
            switch (order[key]) {
×
954
                case "DESC":
955
                    orderCriteria[key] = -1
×
956
                    break
×
957
                case "ASC":
958
                    orderCriteria[key] = 1
×
959
                    break
×
960
                default:
961
                    orderCriteria[key] = order[key]
×
962
            }
963
            return orderCriteria
×
964
        }, {} as ObjectLiteral)
965
    }
966

967
    /**
968
     * Converts FindOptions into mongodb select by criteria.
969
     */
970
    protected convertFindOptionsSelectToProjectCriteria(
971
        selects: FindOptionsSelect<any> | FindOptionsSelectByString<any>,
972
    ) {
973
        if (Array.isArray(selects)) {
×
974
            return selects.reduce((projectCriteria, key) => {
×
975
                projectCriteria[key] = 1
×
976
                return projectCriteria
×
977
            }, {} as any)
978
        } else {
979
            // todo: implement
980
            return {}
×
981
        }
982
    }
983

984
    /**
985
     * Ensures given id is an id for query.
986
     */
987
    protected convertMixedCriteria(
988
        metadata: EntityMetadata,
989
        idMap: any,
990
    ): ObjectLiteral {
991
        const objectIdInstance = PlatformTools.load("mongodb").ObjectId
×
992

993
        // check first if it's ObjectId compatible:
994
        // string, number, Buffer, ObjectId or ObjectId-like
995
        if (objectIdInstance.isValid(idMap)) {
×
996
            return {
×
997
                _id: new objectIdInstance(idMap),
998
            }
999
        }
1000

1001
        // if it's some other type of object build a query from the columns
1002
        // this check needs to be after the ObjectId check, because a valid ObjectId is also an Object instance
1003
        if (ObjectUtils.isObject(idMap)) {
×
1004
            return metadata.columns.reduce((query, column) => {
×
1005
                const columnValue = column.getEntityValue(idMap)
×
1006
                if (columnValue !== undefined)
×
1007
                    query[column.databasePath] = columnValue
×
1008
                return query
×
1009
            }, {} as any)
1010
        }
1011

1012
        // last resort: try to convert it to an ObjectId anyway
1013
        // most likely it will fail, but we want to be backwards compatible and keep the same thrown Errors.
1014
        // it can still pass with null/undefined
1015
        return {
×
1016
            _id: new objectIdInstance(idMap),
1017
        }
1018
    }
1019

1020
    /**
1021
     * Overrides cursor's toArray and next methods to convert results to entity automatically.
1022
     */
1023
    protected applyEntityTransformationToCursor<Entity extends ObjectLiteral>(
1024
        metadata: EntityMetadata,
1025
        cursor: FindCursor<Entity> | AggregationCursor<Entity>,
1026
    ) {
1027
        const queryRunner = this.mongoQueryRunner
×
1028

1029
        ;(cursor as any)["__to_array_func"] = cursor.toArray
×
1030
        cursor.toArray = async () =>
×
1031
            ((cursor as any)["__to_array_func"] as CallableFunction)().then(
×
1032
                async (results: Entity[]) => {
1033
                    const transformer = new DocumentToEntityTransformer()
×
1034
                    const entities = transformer.transformAll(results, metadata)
×
1035
                    // broadcast "load" events
1036
                    await queryRunner.broadcaster.broadcast(
×
1037
                        "Load",
1038
                        metadata,
1039
                        entities,
1040
                    )
1041
                    return entities
×
1042
                },
1043
            )
1044
        ;(cursor as any)["__next_func"] = cursor.next
×
1045
        cursor.next = async () =>
×
1046
            ((cursor as any)["__next_func"] as CallableFunction)().then(
×
1047
                async (result: Entity) => {
1048
                    if (!result) {
×
1049
                        return result
×
1050
                    }
1051
                    const transformer = new DocumentToEntityTransformer()
×
1052
                    const entity = transformer.transform(result, metadata)
×
1053
                    // broadcast "load" events
1054
                    await queryRunner.broadcaster.broadcast("Load", metadata, [
×
1055
                        entity,
1056
                    ])
1057
                    return entity
×
1058
                },
1059
            )
1060
    }
1061

1062
    protected filterSoftDeleted<Entity>(
1063
        cursor: FindCursor<Entity>,
1064
        deleteDateColumn: ColumnMetadata,
1065
        query?: ObjectLiteral,
1066
    ) {
1067
        const { $or, ...restQuery } = query ?? {}
×
1068
        cursor.filter({
×
1069
            $or: [
1070
                { [deleteDateColumn.propertyName]: { $eq: null } },
1071
                ...(Array.isArray($or) ? $or : []),
×
1072
            ],
1073
            ...restQuery,
1074
        })
1075
    }
1076

1077
    /**
1078
     * Finds first entity that matches given conditions and/or find options.
1079
     */
1080
    protected async executeFindOne<Entity>(
1081
        entityClassOrName: EntityTarget<Entity>,
1082
        optionsOrConditions?: any,
1083
        maybeOptions?: MongoFindOneOptions<Entity>,
1084
    ): Promise<Entity | null> {
1085
        const objectIdInstance = PlatformTools.load("mongodb").ObjectId
×
1086
        const id =
1087
            optionsOrConditions instanceof objectIdInstance ||
×
1088
            typeof optionsOrConditions === "string"
1089
                ? optionsOrConditions
1090
                : undefined
1091
        const findOneOptionsOrConditions = (
1092
            id ? maybeOptions : optionsOrConditions
×
1093
        ) as any
1094
        const query =
1095
            this.convertFindOneOptionsOrConditionsToMongodbQuery(
×
1096
                findOneOptionsOrConditions,
1097
            ) || {}
1098
        if (id) {
×
1099
            query["_id"] =
×
1100
                id instanceof objectIdInstance ? id : new objectIdInstance(id)
×
1101
        }
1102
        const cursor = this.createEntityCursor<Entity>(entityClassOrName, query)
×
1103
        const deleteDateColumn =
1104
            this.connection.getMetadata(entityClassOrName).deleteDateColumn
×
1105
        if (FindOptionsUtils.isFindOneOptions(findOneOptionsOrConditions)) {
×
1106
            if (findOneOptionsOrConditions.select)
×
1107
                cursor.project(
×
1108
                    this.convertFindOptionsSelectToProjectCriteria(
1109
                        findOneOptionsOrConditions.select,
1110
                    ),
1111
                )
1112
            if (findOneOptionsOrConditions.order)
×
1113
                cursor.sort(
×
1114
                    this.convertFindOptionsOrderToOrderCriteria(
1115
                        findOneOptionsOrConditions.order,
1116
                    ),
1117
                )
1118
            if (deleteDateColumn && !findOneOptionsOrConditions.withDeleted) {
×
1119
                this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1120
            }
1121
        } else if (deleteDateColumn) {
×
1122
            this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1123
        }
1124

1125
        // const result = await cursor.limit(1).next();
1126
        const result = await cursor.limit(1).toArray()
×
1127
        return result.length > 0 ? result[0] : null
×
1128
    }
1129

1130
    protected async executeFind<Entity>(
1131
        entityClassOrName: EntityTarget<Entity>,
1132
        optionsOrConditions?:
1133
            | MongoFindManyOptions<Entity>
1134
            | Partial<Entity>
1135
            | any[],
1136
    ): Promise<Entity[]> {
1137
        const query =
1138
            this.convertFindManyOptionsOrConditionsToMongodbQuery(
×
1139
                optionsOrConditions,
1140
            )
1141
        const cursor = this.createEntityCursor<Entity>(entityClassOrName, query)
×
1142
        const deleteDateColumn =
1143
            this.connection.getMetadata(entityClassOrName).deleteDateColumn
×
1144

1145
        if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
×
1146
            if (optionsOrConditions.select)
×
1147
                cursor.project(
×
1148
                    this.convertFindOptionsSelectToProjectCriteria(
1149
                        optionsOrConditions.select,
1150
                    ),
1151
                )
1152
            if (optionsOrConditions.skip) cursor.skip(optionsOrConditions.skip)
×
1153
            if (optionsOrConditions.take) cursor.limit(optionsOrConditions.take)
×
1154
            if (optionsOrConditions.order)
×
1155
                cursor.sort(
×
1156
                    this.convertFindOptionsOrderToOrderCriteria(
1157
                        optionsOrConditions.order,
1158
                    ),
1159
                )
1160
            if (deleteDateColumn && !optionsOrConditions.withDeleted) {
×
1161
                this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1162
            }
1163
        } else if (deleteDateColumn) {
×
1164
            this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1165
        }
1166
        return cursor.toArray()
×
1167
    }
1168

1169
    /**
1170
     * Finds entities that match given find options or conditions.
1171
     */
1172
    async executeFindAndCount<Entity>(
1173
        entityClassOrName: EntityTarget<Entity>,
1174
        optionsOrConditions?: MongoFindManyOptions<Entity> | Partial<Entity>,
1175
    ): Promise<[Entity[], number]> {
1176
        const query =
1177
            this.convertFindManyOptionsOrConditionsToMongodbQuery(
×
1178
                optionsOrConditions,
1179
            )
1180
        const cursor = await this.createEntityCursor(entityClassOrName, query)
×
1181
        const deleteDateColumn =
1182
            this.connection.getMetadata(entityClassOrName).deleteDateColumn
×
1183

1184
        if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
×
1185
            if (optionsOrConditions.select)
×
1186
                cursor.project(
×
1187
                    this.convertFindOptionsSelectToProjectCriteria(
1188
                        optionsOrConditions.select,
1189
                    ),
1190
                )
1191
            if (optionsOrConditions.skip) cursor.skip(optionsOrConditions.skip)
×
1192
            if (optionsOrConditions.take) cursor.limit(optionsOrConditions.take)
×
1193
            if (optionsOrConditions.order)
×
1194
                cursor.sort(
×
1195
                    this.convertFindOptionsOrderToOrderCriteria(
1196
                        optionsOrConditions.order,
1197
                    ),
1198
                )
1199
            if (deleteDateColumn && !optionsOrConditions.withDeleted) {
×
1200
                this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1201
            }
1202
        } else if (deleteDateColumn) {
×
1203
            this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1204
        }
1205
        const [results, count] = await Promise.all<any>([
×
1206
            cursor.toArray(),
1207
            this.count(entityClassOrName, query),
1208
        ])
1209
        return [results, parseInt(count)]
×
1210
    }
1211
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc