@onivoro/server-typeorm-mysql 24.0.0 → 24.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +297 -187
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -92,20 +92,21 @@ export class User {
92
92
  ```typescript
93
93
  import { Injectable } from '@nestjs/common';
94
94
  import { TypeOrmRepository, TypeOrmPagingRepository } from '@onivoro/server-typeorm-mysql';
95
+ import { EntityManager } from 'typeorm';
95
96
  import { User } from './user.entity';
96
97
 
97
98
  @Injectable()
98
99
  export class UserRepository extends TypeOrmPagingRepository<User> {
99
- constructor() {
100
- super(User);
100
+ constructor(entityManager: EntityManager) {
101
+ super(User, entityManager);
101
102
  }
102
103
 
103
104
  async findByEmail(email: string): Promise<User | null> {
104
- return this.findOne({ where: { email } });
105
+ return this.getOne({ where: { email } });
105
106
  }
106
107
 
107
108
  async findActiveUsers(): Promise<User[]> {
108
- return this.find({ where: { isActive: true } });
109
+ return this.getMany({ where: { isActive: true } });
109
110
  }
110
111
 
111
112
  async findUsersWithPagination(page: number, limit: number) {
@@ -168,33 +169,98 @@ export class DatabaseModule {}
168
169
 
169
170
  ## Usage Examples
170
171
 
171
- ### Advanced Repository Usage
172
+ ### Basic Repository Operations
173
+
174
+ ```typescript
175
+ import { Injectable } from '@nestjs/common';
176
+ import { TypeOrmRepository } from '@onivoro/server-typeorm-mysql';
177
+ import { EntityManager } from 'typeorm';
178
+ import { User } from './user.entity';
179
+
180
+ @Injectable()
181
+ export class UserRepository extends TypeOrmRepository<User> {
182
+ constructor(entityManager: EntityManager) {
183
+ super(User, entityManager);
184
+ }
185
+
186
+ // Create a single user
187
+ async createUser(userData: Partial<User>): Promise<User> {
188
+ return this.postOne(userData);
189
+ }
190
+
191
+ // Create multiple users
192
+ async createUsers(usersData: Partial<User>[]): Promise<User[]> {
193
+ return this.postMany(usersData);
194
+ }
195
+
196
+ // Find users with filters
197
+ async findUsers(filters: { isActive?: boolean; email?: string }): Promise<User[]> {
198
+ return this.getMany({ where: filters });
199
+ }
200
+
201
+ // Find users with count
202
+ async findUsersWithCount(filters: { isActive?: boolean }): Promise<[User[], number]> {
203
+ return this.getManyAndCount({ where: filters });
204
+ }
205
+
206
+ // Find a single user
207
+ async findUserById(id: number): Promise<User> {
208
+ return this.getOne({ where: { id } });
209
+ }
210
+
211
+ // Update user
212
+ async updateUser(id: number, updateData: Partial<User>): Promise<void> {
213
+ await this.patch({ id }, updateData);
214
+ }
215
+
216
+ // Replace user data
217
+ async replaceUser(id: number, userData: Partial<User>): Promise<void> {
218
+ await this.put({ id }, userData);
219
+ }
220
+
221
+ // Delete user permanently
222
+ async deleteUser(id: number): Promise<void> {
223
+ await this.delete({ id });
224
+ }
225
+
226
+ // Soft delete user
227
+ async softDeleteUser(id: number): Promise<void> {
228
+ await this.softDelete({ id });
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### Advanced Repository Usage with Pagination
172
234
 
173
235
  ```typescript
174
236
  import { Injectable } from '@nestjs/common';
175
237
  import { TypeOrmPagingRepository, PageParams, PagedData } from '@onivoro/server-typeorm-mysql';
238
+ import { EntityManager, Like, Between } from 'typeorm';
176
239
  import { User } from './user.entity';
177
- import { FindOptionsWhere, Like, Between } from 'typeorm';
178
240
 
179
241
  @Injectable()
180
242
  export class AdvancedUserRepository extends TypeOrmPagingRepository<User> {
181
- constructor() {
182
- super(User);
243
+ constructor(entityManager: EntityManager) {
244
+ super(User, entityManager);
183
245
  }
184
246
 
185
247
  async searchUsers(
186
248
  searchTerm: string,
187
249
  pageParams: PageParams
188
250
  ): Promise<PagedData<User>> {
189
- const where: FindOptionsWhere<User> = [
190
- { firstName: Like(`%${searchTerm}%`) },
191
- { lastName: Like(`%${searchTerm}%`) },
192
- { email: Like(`%${searchTerm}%`) }
193
- ];
251
+ const whereConditions = this.buildWhereILike({
252
+ firstName: searchTerm,
253
+ lastName: searchTerm,
254
+ email: searchTerm
255
+ });
194
256
 
195
257
  return this.findWithPaging(
196
258
  {
197
- where,
259
+ where: [
260
+ { firstName: Like(`%${searchTerm}%`) },
261
+ { lastName: Like(`%${searchTerm}%`) },
262
+ { email: Like(`%${searchTerm}%`) }
263
+ ],
198
264
  order: { createdAt: 'DESC' }
199
265
  },
200
266
  pageParams
@@ -221,7 +287,7 @@ export class AdvancedUserRepository extends TypeOrmPagingRepository<User> {
221
287
  const cutoffDate = new Date();
222
288
  cutoffDate.setDate(cutoffDate.getDate() - days);
223
289
 
224
- return this.find({
290
+ return this.getMany({
225
291
  where: {
226
292
  lastLoginAt: Between(cutoffDate, new Date())
227
293
  },
@@ -235,24 +301,24 @@ export class AdvancedUserRepository extends TypeOrmPagingRepository<User> {
235
301
  inactive: number;
236
302
  recentlyRegistered: number;
237
303
  }> {
238
- const [total, active, recentlyRegistered] = await Promise.all([
239
- this.count(),
240
- this.count({ where: { isActive: true } }),
241
- this.count({
242
- where: {
243
- createdAt: Between(
244
- new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago
245
- new Date()
246
- )
247
- }
248
- })
249
- ]);
304
+ const [allUsers, totalCount] = await this.getManyAndCount({});
305
+ const [activeUsers, activeCount] = await this.getManyAndCount({
306
+ where: { isActive: true }
307
+ });
308
+ const [recentUsers, recentCount] = await this.getManyAndCount({
309
+ where: {
310
+ createdAt: Between(
311
+ new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago
312
+ new Date()
313
+ )
314
+ }
315
+ });
250
316
 
251
317
  return {
252
- total,
253
- active,
254
- inactive: total - active,
255
- recentlyRegistered
318
+ total: totalCount,
319
+ active: activeCount,
320
+ inactive: totalCount - activeCount,
321
+ recentlyRegistered: recentCount
256
322
  };
257
323
  }
258
324
 
@@ -260,11 +326,15 @@ export class AdvancedUserRepository extends TypeOrmPagingRepository<User> {
260
326
  userIds: number[],
261
327
  updateData: Partial<User>
262
328
  ): Promise<void> {
263
- await this.update(userIds, updateData);
329
+ for (const id of userIds) {
330
+ await this.patch({ id }, updateData);
331
+ }
264
332
  }
265
333
 
266
334
  async softDeleteUsers(userIds: number[]): Promise<void> {
267
- await this.update(userIds, { isActive: false });
335
+ for (const id of userIds) {
336
+ await this.softDelete({ id });
337
+ }
268
338
  }
269
339
  }
270
340
  ```
@@ -352,7 +422,6 @@ export class OrderItem {
352
422
 
353
423
  ```typescript
354
424
  import { Injectable, NotFoundException } from '@nestjs/common';
355
- import { InjectRepository } from '@nestjs/typeorm';
356
425
  import { AdvancedUserRepository } from './user.repository';
357
426
  import { User } from './user.entity';
358
427
  import { PageParams, PagedData } from '@onivoro/server-typeorm-mysql';
@@ -360,32 +429,29 @@ import { PageParams, PagedData } from '@onivoro/server-typeorm-mysql';
360
429
  @Injectable()
361
430
  export class UserService {
362
431
  constructor(
363
- @InjectRepository(User)
364
432
  private userRepository: AdvancedUserRepository
365
433
  ) {}
366
434
 
367
435
  async createUser(userData: Partial<User>): Promise<User> {
368
- const user = this.userRepository.create(userData);
369
- return this.userRepository.save(user);
436
+ return this.userRepository.postOne(userData);
370
437
  }
371
438
 
372
439
  async findUserById(id: number): Promise<User> {
373
- const user = await this.userRepository.findOne({ where: { id } });
440
+ const user = await this.userRepository.getOne({ where: { id } });
374
441
  if (!user) {
375
442
  throw new NotFoundException(`User with ID ${id} not found`);
376
443
  }
377
444
  return user;
378
445
  }
379
446
 
380
- async updateUser(id: number, updateData: Partial<User>): Promise<User> {
447
+ async updateUser(id: number, updateData: Partial<User>): Promise<void> {
381
448
  const user = await this.findUserById(id);
382
- Object.assign(user, updateData);
383
- return this.userRepository.save(user);
449
+ await this.userRepository.patch({ id }, updateData);
384
450
  }
385
451
 
386
452
  async deleteUser(id: number): Promise<void> {
387
453
  const user = await this.findUserById(id);
388
- await this.userRepository.softDeleteUsers([id]);
454
+ await this.userRepository.softDelete({ id });
389
455
  }
390
456
 
391
457
  async searchUsers(
@@ -417,18 +483,17 @@ import {
417
483
  generateDateQuery,
418
484
  removeFalseyKeys,
419
485
  getSkip,
420
- getPagingKey
486
+ getPagingKey,
487
+ TypeOrmRepository
421
488
  } from '@onivoro/server-typeorm-mysql';
422
- import { Repository } from 'typeorm';
423
- import { InjectRepository } from '@nestjs/typeorm';
489
+ import { EntityManager } from 'typeorm';
424
490
  import { Order } from './order.entity';
425
491
 
426
492
  @Injectable()
427
- export class OrderService {
428
- constructor(
429
- @InjectRepository(Order)
430
- private orderRepository: Repository<Order>
431
- ) {}
493
+ export class OrderService extends TypeOrmRepository<Order> {
494
+ constructor(entityManager: EntityManager) {
495
+ super(Order, entityManager);
496
+ }
432
497
 
433
498
  async findOrdersByDateRange(
434
499
  startDate?: Date,
@@ -444,7 +509,7 @@ export class OrderService {
444
509
 
445
510
  const skip = getSkip(page, limit);
446
511
 
447
- const [orders, total] = await this.orderRepository.findAndCount({
512
+ const [orders, total] = await this.getManyAndCount({
448
513
  where: whereConditions,
449
514
  skip,
450
515
  take: limit,
@@ -467,7 +532,7 @@ export class OrderService {
467
532
  async getOrderAnalytics(startDate: Date, endDate: Date) {
468
533
  const dateQuery = generateDateQuery('createdAt', startDate, endDate);
469
534
 
470
- const queryBuilder = this.orderRepository.createQueryBuilder('order')
535
+ const queryBuilder = this.repo.createQueryBuilder('order')
471
536
  .where(dateQuery);
472
537
 
473
538
  const [
@@ -509,29 +574,32 @@ export class OrderService {
509
574
 
510
575
  ```typescript
511
576
  import { Injectable } from '@nestjs/common';
512
- import { DataSource } from 'typeorm';
577
+ import { EntityManager } from 'typeorm';
578
+ import { TypeOrmRepository } from '@onivoro/server-typeorm-mysql';
513
579
  import { User } from './user.entity';
514
580
  import { Order } from './order.entity';
515
581
  import { OrderItem } from './order-item.entity';
516
582
 
517
583
  @Injectable()
518
584
  export class OrderTransactionService {
519
- constructor(private dataSource: DataSource) {}
585
+ constructor(private entityManager: EntityManager) {}
520
586
 
521
587
  async createOrderWithItems(
522
588
  userId: number,
523
589
  orderData: Partial<Order>,
524
590
  items: Array<{productId: number, quantity: number, unitPrice: number}>
525
591
  ): Promise<Order> {
526
- return this.dataSource.transaction(async manager => {
592
+ return this.entityManager.transaction(async transactionalEntityManager => {
593
+ const orderRepo = new TypeOrmRepository<Order>(Order, transactionalEntityManager);
594
+ const orderItemRepo = new TypeOrmRepository<OrderItem>(OrderItem, transactionalEntityManager);
595
+ const userRepo = new TypeOrmRepository<User>(User, transactionalEntityManager);
596
+
527
597
  // Create the order
528
- const order = manager.create(Order, {
598
+ const order = await orderRepo.postOne({
529
599
  ...orderData,
530
600
  userId,
531
601
  totalAmount: 0 // Will be calculated
532
602
  });
533
-
534
- const savedOrder = await manager.save(order);
535
603
 
536
604
  // Create order items
537
605
  let totalAmount = 0;
@@ -541,27 +609,26 @@ export class OrderTransactionService {
541
609
  const totalPrice = itemData.quantity * itemData.unitPrice;
542
610
  totalAmount += totalPrice;
543
611
 
544
- const orderItem = manager.create(OrderItem, {
545
- orderId: savedOrder.id,
612
+ const orderItem = await orderItemRepo.postOne({
613
+ orderId: order.id,
546
614
  productId: itemData.productId,
547
615
  quantity: itemData.quantity,
548
616
  unitPrice: itemData.unitPrice,
549
617
  totalPrice
550
618
  });
551
619
 
552
- orderItems.push(await manager.save(orderItem));
620
+ orderItems.push(orderItem);
553
621
  }
554
622
 
555
623
  // Update order total
556
- savedOrder.totalAmount = totalAmount;
557
- await manager.save(savedOrder);
624
+ await orderRepo.patch({ id: order.id }, { totalAmount });
558
625
 
559
626
  // Update user's last order date
560
- await manager.update(User, userId, {
627
+ await userRepo.patch({ id: userId }, {
561
628
  updatedAt: new Date()
562
629
  });
563
630
 
564
- return savedOrder;
631
+ return order;
565
632
  });
566
633
  }
567
634
 
@@ -569,15 +636,17 @@ export class OrderTransactionService {
569
636
  orderId: number,
570
637
  newUserId: number
571
638
  ): Promise<void> {
572
- await this.dataSource.transaction(async manager => {
639
+ await this.entityManager.transaction(async transactionalEntityManager => {
640
+ const orderRepo = new TypeOrmRepository<Order>(Order, transactionalEntityManager);
641
+
573
642
  // Update order
574
- await manager.update(Order, orderId, {
643
+ await orderRepo.patch({ id: orderId }, {
575
644
  userId: newUserId,
576
645
  updatedAt: new Date()
577
646
  });
578
647
 
579
- // Log the transfer
580
- await manager.query(
648
+ // Log the transfer using raw query
649
+ await transactionalEntityManager.query(
581
650
  'INSERT INTO order_transfers (order_id, new_user_id, transferred_at) VALUES (?, ?, ?)',
582
651
  [orderId, newUserId, new Date()]
583
652
  );
@@ -586,122 +655,95 @@ export class OrderTransactionService {
586
655
  }
587
656
  ```
588
657
 
589
- ### Custom Query Builder
658
+ ### Query Streaming
590
659
 
591
660
  ```typescript
592
661
  import { Injectable } from '@nestjs/common';
593
- import { Repository, SelectQueryBuilder } from 'typeorm';
594
- import { InjectRepository } from '@nestjs/typeorm';
595
- import { Order } from './order.entity';
662
+ import { TypeOrmRepository } from '@onivoro/server-typeorm-mysql';
663
+ import { EntityManager, QueryRunner } from 'typeorm';
664
+ import { User } from './user.entity';
665
+ import { createWriteStream } from 'fs';
596
666
 
597
667
  @Injectable()
598
- export class OrderQueryService {
599
- constructor(
600
- @InjectRepository(Order)
601
- private orderRepository: Repository<Order>
602
- ) {}
603
-
604
- createBaseQuery(): SelectQueryBuilder<Order> {
605
- return this.orderRepository
606
- .createQueryBuilder('order')
607
- .leftJoinAndSelect('order.user', 'user')
608
- .leftJoinAndSelect('order.items', 'items')
609
- .leftJoinAndSelect('items.product', 'product');
610
- }
611
-
612
- async findOrdersWithFilters(filters: {
613
- status?: string[];
614
- userIds?: number[];
615
- minAmount?: number;
616
- maxAmount?: number;
617
- startDate?: Date;
618
- endDate?: Date;
619
- limit?: number;
620
- offset?: number;
621
- }) {
622
- let query = this.createBaseQuery();
623
-
624
- if (filters.status?.length) {
625
- query = query.andWhere('order.status IN (:...statuses)', {
626
- statuses: filters.status
627
- });
628
- }
629
-
630
- if (filters.userIds?.length) {
631
- query = query.andWhere('order.userId IN (:...userIds)', {
632
- userIds: filters.userIds
633
- });
634
- }
668
+ export class UserStreamingService extends TypeOrmRepository<User> {
669
+ constructor(entityManager: EntityManager) {
670
+ super(User, entityManager);
671
+ }
635
672
 
636
- if (filters.minAmount !== undefined) {
637
- query = query.andWhere('order.totalAmount >= :minAmount', {
638
- minAmount: filters.minAmount
639
- });
640
- }
673
+ async exportUsersToFile(filePath: string): Promise<void> {
674
+ const writeStream = createWriteStream(filePath);
675
+
676
+ writeStream.write('id,email,firstName,lastName,createdAt\n');
677
+
678
+ const { stream, error } = await this.queryStream({
679
+ query: 'SELECT id, email, firstName, lastName, createdAt FROM users WHERE isActive = 1',
680
+ onData: async (stream, record: User, count) => {
681
+ const csvLine = `${record.id},"${record.email}","${record.firstName}","${record.lastName}","${record.createdAt}"\n`;
682
+ writeStream.write(csvLine);
683
+
684
+ if (count % 1000 === 0) {
685
+ console.log(`Processed ${count} records`);
686
+ }
687
+ },
688
+ onError: async (stream, error) => {
689
+ console.error('Stream error:', error);
690
+ writeStream.end();
691
+ },
692
+ onEnd: async (stream, count) => {
693
+ console.log(`Export completed. Total records: ${count}`);
694
+ writeStream.end();
695
+ }
696
+ });
641
697
 
642
- if (filters.maxAmount !== undefined) {
643
- query = query.andWhere('order.totalAmount <= :maxAmount', {
644
- maxAmount: filters.maxAmount
645
- });
698
+ if (error) {
699
+ throw new Error(`Failed to start streaming: ${error.message}`);
646
700
  }
701
+ }
647
702
 
648
- if (filters.startDate) {
649
- query = query.andWhere('order.createdAt >= :startDate', {
650
- startDate: filters.startDate
651
- });
652
- }
703
+ async processLargeDataset(): Promise<void> {
704
+ const { stream, error } = await this.queryStream({
705
+ query: 'SELECT * FROM users WHERE createdAt > DATE_SUB(NOW(), INTERVAL 1 YEAR)',
706
+ onData: async (stream, record: User, count) => {
707
+ // Process each record individually
708
+ // This is memory efficient for large datasets
709
+ await this.processUserRecord(record);
710
+ },
711
+ onError: async (stream, error) => {
712
+ console.error('Processing error:', error);
713
+ },
714
+ onEnd: async (stream, count) => {
715
+ console.log(`Processed ${count} user records`);
716
+ }
717
+ });
653
718
 
654
- if (filters.endDate) {
655
- query = query.andWhere('order.createdAt <= :endDate', {
656
- endDate: filters.endDate
657
- });
719
+ if (error) {
720
+ throw new Error(`Failed to process dataset: ${error.message}`);
658
721
  }
722
+ }
659
723
 
660
- query = query.orderBy('order.createdAt', 'DESC');
724
+ private async processUserRecord(user: User): Promise<void> {
725
+ // Your custom processing logic here
726
+ console.log(`Processing user: ${user.email}`);
727
+ }
661
728
 
662
- if (filters.limit) {
663
- query = query.take(filters.limit);
664
- }
729
+ // Static method usage for custom query runners
730
+ static async streamWithCustomQueryRunner(
731
+ queryRunner: QueryRunner,
732
+ query: string
733
+ ): Promise<void> {
734
+ const { stream, error } = await TypeOrmRepository.queryStream(queryRunner, {
735
+ query,
736
+ onData: async (stream, record, count) => {
737
+ console.log(`Record ${count}:`, record);
738
+ },
739
+ onEnd: async (stream, count) => {
740
+ console.log(`Stream completed with ${count} records`);
741
+ }
742
+ });
665
743
 
666
- if (filters.offset) {
667
- query = query.skip(filters.offset);
744
+ if (error) {
745
+ console.error('Stream failed:', error);
668
746
  }
669
-
670
- return query.getManyAndCount();
671
- }
672
-
673
- async getOrderSummaryByUser(userId: number) {
674
- return this.orderRepository
675
- .createQueryBuilder('order')
676
- .select([
677
- 'COUNT(order.id) as orderCount',
678
- 'SUM(order.totalAmount) as totalSpent',
679
- 'AVG(order.totalAmount) as averageOrderValue',
680
- 'MAX(order.createdAt) as lastOrderDate',
681
- 'MIN(order.createdAt) as firstOrderDate'
682
- ])
683
- .where('order.userId = :userId', { userId })
684
- .andWhere('order.status != :cancelledStatus', { cancelledStatus: 'cancelled' })
685
- .getRawOne();
686
- }
687
-
688
- async getTopCustomers(limit: number = 10) {
689
- return this.orderRepository
690
- .createQueryBuilder('order')
691
- .leftJoin('order.user', 'user')
692
- .select([
693
- 'user.id as userId',
694
- 'user.firstName as firstName',
695
- 'user.lastName as lastName',
696
- 'user.email as email',
697
- 'COUNT(order.id) as orderCount',
698
- 'SUM(order.totalAmount) as totalSpent'
699
- ])
700
- .where('order.status != :cancelledStatus', { cancelledStatus: 'cancelled' })
701
- .groupBy('user.id')
702
- .orderBy('totalSpent', 'DESC')
703
- .limit(limit)
704
- .getRawMany();
705
747
  }
706
748
  }
707
749
  ```
@@ -715,10 +757,32 @@ export class OrderQueryService {
715
757
  Base repository class with enhanced functionality:
716
758
 
717
759
  ```typescript
718
- export class TypeOrmRepository<T> extends Repository<T> {
719
- constructor(entity: EntityTarget<T>)
760
+ export class TypeOrmRepository<T> {
761
+ constructor(entityType: any, entityManager: EntityManager)
720
762
 
721
- // Enhanced methods with better error handling and utilities
763
+ // Core CRUD methods
764
+ async getMany(options: FindManyOptions<T>): Promise<T[]>
765
+ async getManyAndCount(options: FindManyOptions<T>): Promise<[T[], number]>
766
+ async getOne(options: FindOneOptions<T>): Promise<T>
767
+ async postOne(body: Partial<T>): Promise<T>
768
+ async postMany(body: Partial<T>[]): Promise<T[]>
769
+ async delete(options: FindOptionsWhere<T>): Promise<void>
770
+ async softDelete(options: FindOptionsWhere<T>): Promise<void>
771
+ async put(options: FindOptionsWhere<T>, body: QueryDeepPartialEntity<T>): Promise<void>
772
+ async patch(options: FindOptionsWhere<T>, body: QueryDeepPartialEntity<T>): Promise<void>
773
+
774
+ // Transaction support
775
+ forTransaction(entityManager: EntityManager): TypeOrmRepository<T>
776
+
777
+ // Streaming support
778
+ async queryStream<TRecord = any>(params: TQueryStreamParams): Promise<{stream: any, error: any}>
779
+ static async queryStream<TRecord = any>(queryRunner: QueryRunner, params: TQueryStreamParams): Promise<{stream: any, error: any}>
780
+
781
+ // Utility methods
782
+ buildWhereILike(filters?: Record<string, any>): FindOptionsWhere<T>
783
+
784
+ // Internal properties
785
+ get repo(): Repository<T>
722
786
  }
723
787
  ```
724
788
 
@@ -832,14 +896,27 @@ interface PagedData<T> {
832
896
  }
833
897
  ```
834
898
 
899
+ #### TQueryStreamParams
900
+
901
+ Query streaming parameters:
902
+
903
+ ```typescript
904
+ type TQueryStreamParams<TRecord = any> = {
905
+ query: string;
906
+ onData?: (stream: ReadStream, record: TRecord, count: number) => Promise<any | void>;
907
+ onError?: (stream: ReadStream, error: any) => Promise<any | void>;
908
+ onEnd?: (stream: ReadStream, count: number) => Promise<any | void>;
909
+ };
910
+ ```
911
+
835
912
  ## Best Practices
836
913
 
837
- 1. **Repository Pattern**: Use custom repositories for complex queries
838
- 2. **Transactions**: Use transactions for multi-table operations
914
+ 1. **Repository Pattern**: Use custom repositories extending TypeOrmRepository for domain-specific operations
915
+ 2. **Transactions**: Use `forTransaction()` method for multi-table operations
839
916
  3. **Indexing**: Add proper indexes for frequently queried columns
840
- 4. **Pagination**: Always implement pagination for list endpoints
841
- 5. **Query Optimization**: Use query builders for complex queries
842
- 6. **Error Handling**: Implement proper error handling in repositories
917
+ 4. **Pagination**: Always implement pagination using TypeOrmPagingRepository for list operations
918
+ 5. **Streaming**: Use `queryStream()` for processing large datasets efficiently
919
+ 6. **Error Handling**: Implement proper error handling in repositories and services
843
920
  7. **Type Safety**: Leverage TypeScript for type-safe database operations
844
921
  8. **Connection Pooling**: Configure appropriate connection pool settings
845
922
 
@@ -847,28 +924,53 @@ interface PagedData<T> {
847
924
 
848
925
  ```typescript
849
926
  import { Test } from '@nestjs/testing';
850
- import { getRepositoryToken } from '@nestjs/typeorm';
851
- import { Repository } from 'typeorm';
927
+ import { EntityManager } from 'typeorm';
852
928
  import { User } from './user.entity';
853
929
  import { UserService } from './user.service';
930
+ import { AdvancedUserRepository } from './user.repository';
854
931
 
855
932
  describe('UserService', () => {
856
933
  let service: UserService;
857
- let repository: Repository<User>;
934
+ let repository: AdvancedUserRepository;
935
+ let entityManager: EntityManager;
858
936
 
859
937
  beforeEach(async () => {
938
+ const mockEntityManager = {
939
+ getRepository: jest.fn().mockReturnValue({
940
+ find: jest.fn(),
941
+ findAndCount: jest.fn(),
942
+ findOne: jest.fn(),
943
+ save: jest.fn(),
944
+ create: jest.fn(),
945
+ update: jest.fn(),
946
+ delete: jest.fn(),
947
+ softDelete: jest.fn(),
948
+ createQueryBuilder: jest.fn().mockReturnValue({
949
+ insert: jest.fn().mockReturnThis(),
950
+ values: jest.fn().mockReturnThis(),
951
+ returning: jest.fn().mockReturnThis(),
952
+ execute: jest.fn()
953
+ })
954
+ })
955
+ };
956
+
860
957
  const module = await Test.createTestingModule({
861
958
  providers: [
862
959
  UserService,
863
960
  {
864
- provide: getRepositoryToken(User),
865
- useClass: Repository,
961
+ provide: AdvancedUserRepository,
962
+ useFactory: () => new AdvancedUserRepository(mockEntityManager as any)
866
963
  },
964
+ {
965
+ provide: EntityManager,
966
+ useValue: mockEntityManager
967
+ }
867
968
  ],
868
969
  }).compile();
869
970
 
870
971
  service = module.get<UserService>(UserService);
871
- repository = module.get<Repository<User>>(getRepositoryToken(User));
972
+ repository = module.get<AdvancedUserRepository>(AdvancedUserRepository);
973
+ entityManager = module.get<EntityManager>(EntityManager);
872
974
  });
873
975
 
874
976
  it('should create a user', async () => {
@@ -878,11 +980,19 @@ describe('UserService', () => {
878
980
  lastName: 'Doe'
879
981
  };
880
982
 
881
- jest.spyOn(repository, 'create').mockReturnValue(userData as User);
882
- jest.spyOn(repository, 'save').mockResolvedValue(userData as User);
983
+ const createdUser = { id: 1, ...userData };
984
+ jest.spyOn(repository, 'postOne').mockResolvedValue(createdUser as User);
883
985
 
884
986
  const result = await service.createUser(userData);
885
- expect(result).toEqual(userData);
987
+ expect(result).toEqual(createdUser);
988
+ });
989
+
990
+ it('should find user by id', async () => {
991
+ const user = { id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe' };
992
+ jest.spyOn(repository, 'getOne').mockResolvedValue(user as User);
993
+
994
+ const result = await service.findUserById(1);
995
+ expect(result).toEqual(user);
886
996
  });
887
997
  });
888
998
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onivoro/server-typeorm-mysql",
3
- "version": "24.0.0",
3
+ "version": "24.0.1",
4
4
  "type": "commonjs",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",