@onivoro/server-typeorm-mysql 22.0.4 → 24.0.0

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 (124) hide show
  1. package/README.md +892 -0
  2. package/{dist/cjs/index.d.ts → index.ts} +6 -1
  3. package/jest.config.ts +11 -0
  4. package/package.json +8 -47
  5. package/project.json +23 -0
  6. package/{dist/esm/index.d.ts → src/index.ts} +6 -1
  7. package/src/lib/classes/__snapshots__/sql-writer.class.spec.ts.snap +8 -0
  8. package/src/lib/classes/type-orm-paging-repository.class.ts +22 -0
  9. package/src/lib/classes/type-orm-repository.class.ts +152 -0
  10. package/src/lib/constants/many-to-one-relation-options.constant.ts +3 -0
  11. package/src/lib/decorators/nullable-table-column.decorator.ts +9 -0
  12. package/src/lib/decorators/primary-table-column.decorator.ts +9 -0
  13. package/src/lib/decorators/table-column.decorator.ts +9 -0
  14. package/src/lib/decorators/table.decorator.ts +8 -0
  15. package/src/lib/functions/data-source-config-factory.function.ts +33 -0
  16. package/src/lib/functions/data-source-factory.function.ts +10 -0
  17. package/src/lib/functions/generate-date-query.function.ts +20 -0
  18. package/src/lib/functions/get-api-type-from-column.function.ts +14 -0
  19. package/src/lib/functions/get-paging-key.function.ts +3 -0
  20. package/src/lib/functions/get-skip.function.ts +3 -0
  21. package/src/lib/functions/remove-falsey-keys.function.ts +8 -0
  22. package/src/lib/server-typeorm-mysql.module.ts +59 -0
  23. package/src/lib/types/data-source-options.interface.ts +10 -0
  24. package/{dist/cjs/lib/types/entity-provider.interface.d.ts → src/lib/types/entity-provider.interface.ts} +1 -1
  25. package/{dist/types/lib/types/page-params.interface.d.ts → src/lib/types/page-params.interface.ts} +2 -0
  26. package/tsconfig.json +16 -0
  27. package/tsconfig.lib.json +8 -0
  28. package/tsconfig.spec.json +21 -0
  29. package/dist/cjs/index.js +0 -35
  30. package/dist/cjs/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
  31. package/dist/cjs/lib/classes/type-orm-paging-repository.class.js +0 -16
  32. package/dist/cjs/lib/classes/type-orm-repository.class.d.ts +0 -42
  33. package/dist/cjs/lib/classes/type-orm-repository.class.js +0 -93
  34. package/dist/cjs/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
  35. package/dist/cjs/lib/constants/many-to-one-relation-options.constant.js +0 -4
  36. package/dist/cjs/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
  37. package/dist/cjs/lib/decorators/nullable-table-column.decorator.js +0 -12
  38. package/dist/cjs/lib/decorators/primary-table-column.decorator.d.ts +0 -2
  39. package/dist/cjs/lib/decorators/primary-table-column.decorator.js +0 -12
  40. package/dist/cjs/lib/decorators/table-column.decorator.d.ts +0 -2
  41. package/dist/cjs/lib/decorators/table-column.decorator.js +0 -12
  42. package/dist/cjs/lib/decorators/table.decorator.d.ts +0 -3
  43. package/dist/cjs/lib/decorators/table.decorator.js +0 -11
  44. package/dist/cjs/lib/functions/data-source-config-factory.function.d.ts +0 -3
  45. package/dist/cjs/lib/functions/data-source-config-factory.function.js +0 -24
  46. package/dist/cjs/lib/functions/data-source-factory.function.d.ts +0 -3
  47. package/dist/cjs/lib/functions/data-source-factory.function.js +0 -7
  48. package/dist/cjs/lib/functions/generate-date-query.function.d.ts +0 -2
  49. package/dist/cjs/lib/functions/generate-date-query.function.js +0 -16
  50. package/dist/cjs/lib/functions/get-api-type-from-column.function.d.ts +0 -2
  51. package/dist/cjs/lib/functions/get-api-type-from-column.function.js +0 -14
  52. package/dist/cjs/lib/functions/get-paging-key.function.d.ts +0 -1
  53. package/dist/cjs/lib/functions/get-paging-key.function.js +0 -6
  54. package/dist/cjs/lib/functions/get-skip.function.d.ts +0 -1
  55. package/dist/cjs/lib/functions/get-skip.function.js +0 -6
  56. package/dist/cjs/lib/functions/remove-falsey-keys.function.d.ts +0 -1
  57. package/dist/cjs/lib/functions/remove-falsey-keys.function.js +0 -11
  58. package/dist/cjs/lib/server-typeorm-mysql.module.d.ts +0 -6
  59. package/dist/cjs/lib/server-typeorm-mysql.module.js +0 -63
  60. package/dist/cjs/lib/types/data-source-options.interface.d.ts +0 -10
  61. package/dist/cjs/lib/types/data-source-options.interface.js +0 -2
  62. package/dist/cjs/lib/types/entity-provider.interface.js +0 -2
  63. package/dist/cjs/lib/types/page-params.interface.d.ts +0 -4
  64. package/dist/cjs/lib/types/page-params.interface.js +0 -2
  65. package/dist/cjs/lib/types/paged-data.interface.js +0 -2
  66. package/dist/esm/index.js +0 -35
  67. package/dist/esm/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
  68. package/dist/esm/lib/classes/type-orm-paging-repository.class.js +0 -16
  69. package/dist/esm/lib/classes/type-orm-repository.class.d.ts +0 -42
  70. package/dist/esm/lib/classes/type-orm-repository.class.js +0 -93
  71. package/dist/esm/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
  72. package/dist/esm/lib/constants/many-to-one-relation-options.constant.js +0 -4
  73. package/dist/esm/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
  74. package/dist/esm/lib/decorators/nullable-table-column.decorator.js +0 -12
  75. package/dist/esm/lib/decorators/primary-table-column.decorator.d.ts +0 -2
  76. package/dist/esm/lib/decorators/primary-table-column.decorator.js +0 -12
  77. package/dist/esm/lib/decorators/table-column.decorator.d.ts +0 -2
  78. package/dist/esm/lib/decorators/table-column.decorator.js +0 -12
  79. package/dist/esm/lib/decorators/table.decorator.d.ts +0 -3
  80. package/dist/esm/lib/decorators/table.decorator.js +0 -11
  81. package/dist/esm/lib/functions/data-source-config-factory.function.d.ts +0 -3
  82. package/dist/esm/lib/functions/data-source-config-factory.function.js +0 -24
  83. package/dist/esm/lib/functions/data-source-factory.function.d.ts +0 -3
  84. package/dist/esm/lib/functions/data-source-factory.function.js +0 -7
  85. package/dist/esm/lib/functions/generate-date-query.function.d.ts +0 -2
  86. package/dist/esm/lib/functions/generate-date-query.function.js +0 -16
  87. package/dist/esm/lib/functions/get-api-type-from-column.function.d.ts +0 -2
  88. package/dist/esm/lib/functions/get-api-type-from-column.function.js +0 -14
  89. package/dist/esm/lib/functions/get-paging-key.function.d.ts +0 -1
  90. package/dist/esm/lib/functions/get-paging-key.function.js +0 -6
  91. package/dist/esm/lib/functions/get-skip.function.d.ts +0 -1
  92. package/dist/esm/lib/functions/get-skip.function.js +0 -6
  93. package/dist/esm/lib/functions/remove-falsey-keys.function.d.ts +0 -1
  94. package/dist/esm/lib/functions/remove-falsey-keys.function.js +0 -11
  95. package/dist/esm/lib/server-typeorm-mysql.module.d.ts +0 -6
  96. package/dist/esm/lib/server-typeorm-mysql.module.js +0 -63
  97. package/dist/esm/lib/types/data-source-options.interface.d.ts +0 -10
  98. package/dist/esm/lib/types/data-source-options.interface.js +0 -2
  99. package/dist/esm/lib/types/entity-provider.interface.d.ts +0 -9
  100. package/dist/esm/lib/types/entity-provider.interface.js +0 -2
  101. package/dist/esm/lib/types/page-params.interface.d.ts +0 -4
  102. package/dist/esm/lib/types/page-params.interface.js +0 -2
  103. package/dist/esm/lib/types/paged-data.interface.d.ts +0 -6
  104. package/dist/esm/lib/types/paged-data.interface.js +0 -2
  105. package/dist/types/index.d.ts +0 -19
  106. package/dist/types/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
  107. package/dist/types/lib/classes/type-orm-repository.class.d.ts +0 -42
  108. package/dist/types/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
  109. package/dist/types/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
  110. package/dist/types/lib/decorators/primary-table-column.decorator.d.ts +0 -2
  111. package/dist/types/lib/decorators/table-column.decorator.d.ts +0 -2
  112. package/dist/types/lib/decorators/table.decorator.d.ts +0 -3
  113. package/dist/types/lib/functions/data-source-config-factory.function.d.ts +0 -3
  114. package/dist/types/lib/functions/data-source-factory.function.d.ts +0 -3
  115. package/dist/types/lib/functions/generate-date-query.function.d.ts +0 -2
  116. package/dist/types/lib/functions/get-api-type-from-column.function.d.ts +0 -2
  117. package/dist/types/lib/functions/get-paging-key.function.d.ts +0 -1
  118. package/dist/types/lib/functions/get-skip.function.d.ts +0 -1
  119. package/dist/types/lib/functions/remove-falsey-keys.function.d.ts +0 -1
  120. package/dist/types/lib/server-typeorm-mysql.module.d.ts +0 -6
  121. package/dist/types/lib/types/data-source-options.interface.d.ts +0 -10
  122. package/dist/types/lib/types/entity-provider.interface.d.ts +0 -9
  123. package/dist/types/lib/types/paged-data.interface.d.ts +0 -6
  124. /package/{dist/cjs/lib/types/paged-data.interface.d.ts → src/lib/types/paged-data.interface.ts} +0 -0
package/README.md ADDED
@@ -0,0 +1,892 @@
1
+ # @onivoro/server-typeorm-mysql
2
+
3
+ A comprehensive TypeORM MySQL integration library for NestJS applications, providing custom repositories, decorators, utilities, and enhanced MySQL-specific functionality for enterprise-scale database operations.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @onivoro/server-typeorm-mysql
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **TypeORM MySQL Module**: Complete NestJS module for MySQL integration
14
+ - **Custom Repository Classes**: Enhanced repository patterns with pagination and utilities
15
+ - **Custom Decorators**: MySQL-specific column decorators and table definitions
16
+ - **Data Source Factory**: Flexible data source configuration and creation
17
+ - **Pagination Support**: Built-in pagination utilities and interfaces
18
+ - **Query Utilities**: Helper functions for date queries and data manipulation
19
+ - **Type Safety**: Full TypeScript support with comprehensive type definitions
20
+ - **MySQL Optimizations**: MySQL-specific optimizations and best practices
21
+
22
+ ## Quick Start
23
+
24
+ ### Import the Module
25
+
26
+ ```typescript
27
+ import { ServerTypeormMysqlModule } from '@onivoro/server-typeorm-mysql';
28
+
29
+ @Module({
30
+ imports: [
31
+ ServerTypeormMysqlModule.forRoot({
32
+ host: 'localhost',
33
+ port: 3306,
34
+ username: 'root',
35
+ password: 'password',
36
+ database: 'myapp',
37
+ entities: [User, Product, Order],
38
+ synchronize: false,
39
+ logging: true
40
+ })
41
+ ],
42
+ })
43
+ export class AppModule {}
44
+ ```
45
+
46
+ ### Define Entities with Custom Decorators
47
+
48
+ ```typescript
49
+ import {
50
+ Table,
51
+ PrimaryTableColumn,
52
+ TableColumn,
53
+ NullableTableColumn
54
+ } from '@onivoro/server-typeorm-mysql';
55
+ import { Entity } from 'typeorm';
56
+
57
+ @Entity()
58
+ @Table('users')
59
+ export class User {
60
+ @PrimaryTableColumn()
61
+ id: number;
62
+
63
+ @TableColumn({ type: 'varchar', length: 255 })
64
+ email: string;
65
+
66
+ @TableColumn({ type: 'varchar', length: 100 })
67
+ firstName: string;
68
+
69
+ @TableColumn({ type: 'varchar', length: 100 })
70
+ lastName: string;
71
+
72
+ @NullableTableColumn({ type: 'datetime' })
73
+ lastLoginAt?: Date;
74
+
75
+ @TableColumn({ type: 'boolean', default: true })
76
+ isActive: boolean;
77
+
78
+ @TableColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
79
+ createdAt: Date;
80
+
81
+ @TableColumn({
82
+ type: 'timestamp',
83
+ default: () => 'CURRENT_TIMESTAMP',
84
+ onUpdate: 'CURRENT_TIMESTAMP'
85
+ })
86
+ updatedAt: Date;
87
+ }
88
+ ```
89
+
90
+ ### Use Custom Repository
91
+
92
+ ```typescript
93
+ import { Injectable } from '@nestjs/common';
94
+ import { TypeOrmRepository, TypeOrmPagingRepository } from '@onivoro/server-typeorm-mysql';
95
+ import { User } from './user.entity';
96
+
97
+ @Injectable()
98
+ export class UserRepository extends TypeOrmPagingRepository<User> {
99
+ constructor() {
100
+ super(User);
101
+ }
102
+
103
+ async findByEmail(email: string): Promise<User | null> {
104
+ return this.findOne({ where: { email } });
105
+ }
106
+
107
+ async findActiveUsers(): Promise<User[]> {
108
+ return this.find({ where: { isActive: true } });
109
+ }
110
+
111
+ async findUsersWithPagination(page: number, limit: number) {
112
+ return this.findWithPaging(
113
+ { where: { isActive: true } },
114
+ { page, limit }
115
+ );
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## Configuration
121
+
122
+ ### Data Source Configuration
123
+
124
+ ```typescript
125
+ import { dataSourceConfigFactory } from '@onivoro/server-typeorm-mysql';
126
+
127
+ const config = dataSourceConfigFactory({
128
+ host: process.env.DB_HOST,
129
+ port: parseInt(process.env.DB_PORT),
130
+ username: process.env.DB_USERNAME,
131
+ password: process.env.DB_PASSWORD,
132
+ database: process.env.DB_DATABASE,
133
+ entities: [User, Product, Order],
134
+ migrations: ['src/migrations/*.ts'],
135
+ synchronize: false,
136
+ logging: process.env.NODE_ENV === 'development',
137
+ ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
138
+ });
139
+ ```
140
+
141
+ ### Dynamic Module Configuration
142
+
143
+ ```typescript
144
+ import { Module } from '@nestjs/common';
145
+ import { ServerTypeormMysqlModule } from '@onivoro/server-typeorm-mysql';
146
+ import { ConfigService } from '@nestjs/config';
147
+
148
+ @Module({
149
+ imports: [
150
+ ServerTypeormMysqlModule.forRootAsync({
151
+ useFactory: (configService: ConfigService) => ({
152
+ host: configService.get('DATABASE_HOST'),
153
+ port: configService.get('DATABASE_PORT'),
154
+ username: configService.get('DATABASE_USERNAME'),
155
+ password: configService.get('DATABASE_PASSWORD'),
156
+ database: configService.get('DATABASE_NAME'),
157
+ entities: [__dirname + '/**/*.entity{.ts,.js}'],
158
+ migrations: [__dirname + '/migrations/*{.ts,.js}'],
159
+ synchronize: configService.get('NODE_ENV') === 'development',
160
+ logging: configService.get('DATABASE_LOGGING') === 'true'
161
+ }),
162
+ inject: [ConfigService]
163
+ })
164
+ ],
165
+ })
166
+ export class DatabaseModule {}
167
+ ```
168
+
169
+ ## Usage Examples
170
+
171
+ ### Advanced Repository Usage
172
+
173
+ ```typescript
174
+ import { Injectable } from '@nestjs/common';
175
+ import { TypeOrmPagingRepository, PageParams, PagedData } from '@onivoro/server-typeorm-mysql';
176
+ import { User } from './user.entity';
177
+ import { FindOptionsWhere, Like, Between } from 'typeorm';
178
+
179
+ @Injectable()
180
+ export class AdvancedUserRepository extends TypeOrmPagingRepository<User> {
181
+ constructor() {
182
+ super(User);
183
+ }
184
+
185
+ async searchUsers(
186
+ searchTerm: string,
187
+ pageParams: PageParams
188
+ ): Promise<PagedData<User>> {
189
+ const where: FindOptionsWhere<User> = [
190
+ { firstName: Like(`%${searchTerm}%`) },
191
+ { lastName: Like(`%${searchTerm}%`) },
192
+ { email: Like(`%${searchTerm}%`) }
193
+ ];
194
+
195
+ return this.findWithPaging(
196
+ {
197
+ where,
198
+ order: { createdAt: 'DESC' }
199
+ },
200
+ pageParams
201
+ );
202
+ }
203
+
204
+ async findUsersByDateRange(
205
+ startDate: Date,
206
+ endDate: Date,
207
+ pageParams: PageParams
208
+ ): Promise<PagedData<User>> {
209
+ return this.findWithPaging(
210
+ {
211
+ where: {
212
+ createdAt: Between(startDate, endDate)
213
+ },
214
+ order: { createdAt: 'DESC' }
215
+ },
216
+ pageParams
217
+ );
218
+ }
219
+
220
+ async findRecentlyActiveUsers(days: number = 30): Promise<User[]> {
221
+ const cutoffDate = new Date();
222
+ cutoffDate.setDate(cutoffDate.getDate() - days);
223
+
224
+ return this.find({
225
+ where: {
226
+ lastLoginAt: Between(cutoffDate, new Date())
227
+ },
228
+ order: { lastLoginAt: 'DESC' }
229
+ });
230
+ }
231
+
232
+ async getUserStatistics(): Promise<{
233
+ total: number;
234
+ active: number;
235
+ inactive: number;
236
+ recentlyRegistered: number;
237
+ }> {
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
+ ]);
250
+
251
+ return {
252
+ total,
253
+ active,
254
+ inactive: total - active,
255
+ recentlyRegistered
256
+ };
257
+ }
258
+
259
+ async bulkUpdateUsers(
260
+ userIds: number[],
261
+ updateData: Partial<User>
262
+ ): Promise<void> {
263
+ await this.update(userIds, updateData);
264
+ }
265
+
266
+ async softDeleteUsers(userIds: number[]): Promise<void> {
267
+ await this.update(userIds, { isActive: false });
268
+ }
269
+ }
270
+ ```
271
+
272
+ ### Complex Entity Relationships
273
+
274
+ ```typescript
275
+ import {
276
+ Table,
277
+ PrimaryTableColumn,
278
+ TableColumn,
279
+ NullableTableColumn,
280
+ ManyToOneRelationOptions
281
+ } from '@onivoro/server-typeorm-mysql';
282
+ import { Entity, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
283
+
284
+ @Entity()
285
+ @Table('orders')
286
+ export class Order {
287
+ @PrimaryTableColumn()
288
+ id: number;
289
+
290
+ @TableColumn({ type: 'varchar', length: 50 })
291
+ orderNumber: string;
292
+
293
+ @TableColumn({ type: 'decimal', precision: 10, scale: 2 })
294
+ totalAmount: number;
295
+
296
+ @TableColumn({ type: 'enum', enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'] })
297
+ status: string;
298
+
299
+ @TableColumn({ type: 'int' })
300
+ userId: number;
301
+
302
+ @TableColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
303
+ createdAt: Date;
304
+
305
+ @NullableTableColumn({ type: 'timestamp' })
306
+ shippedAt?: Date;
307
+
308
+ @NullableTableColumn({ type: 'timestamp' })
309
+ deliveredAt?: Date;
310
+
311
+ // Relationships
312
+ @ManyToOne(() => User, user => user.orders, ManyToOneRelationOptions)
313
+ @JoinColumn({ name: 'userId' })
314
+ user: User;
315
+
316
+ @OneToMany(() => OrderItem, orderItem => orderItem.order)
317
+ items: OrderItem[];
318
+ }
319
+
320
+ @Entity()
321
+ @Table('order_items')
322
+ export class OrderItem {
323
+ @PrimaryTableColumn()
324
+ id: number;
325
+
326
+ @TableColumn({ type: 'int' })
327
+ orderId: number;
328
+
329
+ @TableColumn({ type: 'int' })
330
+ productId: number;
331
+
332
+ @TableColumn({ type: 'int' })
333
+ quantity: number;
334
+
335
+ @TableColumn({ type: 'decimal', precision: 10, scale: 2 })
336
+ unitPrice: number;
337
+
338
+ @TableColumn({ type: 'decimal', precision: 10, scale: 2 })
339
+ totalPrice: number;
340
+
341
+ @ManyToOne(() => Order, order => order.items, ManyToOneRelationOptions)
342
+ @JoinColumn({ name: 'orderId' })
343
+ order: Order;
344
+
345
+ @ManyToOne(() => Product, product => product.orderItems, ManyToOneRelationOptions)
346
+ @JoinColumn({ name: 'productId' })
347
+ product: Product;
348
+ }
349
+ ```
350
+
351
+ ### Service Layer with Repository
352
+
353
+ ```typescript
354
+ import { Injectable, NotFoundException } from '@nestjs/common';
355
+ import { InjectRepository } from '@nestjs/typeorm';
356
+ import { AdvancedUserRepository } from './user.repository';
357
+ import { User } from './user.entity';
358
+ import { PageParams, PagedData } from '@onivoro/server-typeorm-mysql';
359
+
360
+ @Injectable()
361
+ export class UserService {
362
+ constructor(
363
+ @InjectRepository(User)
364
+ private userRepository: AdvancedUserRepository
365
+ ) {}
366
+
367
+ async createUser(userData: Partial<User>): Promise<User> {
368
+ const user = this.userRepository.create(userData);
369
+ return this.userRepository.save(user);
370
+ }
371
+
372
+ async findUserById(id: number): Promise<User> {
373
+ const user = await this.userRepository.findOne({ where: { id } });
374
+ if (!user) {
375
+ throw new NotFoundException(`User with ID ${id} not found`);
376
+ }
377
+ return user;
378
+ }
379
+
380
+ async updateUser(id: number, updateData: Partial<User>): Promise<User> {
381
+ const user = await this.findUserById(id);
382
+ Object.assign(user, updateData);
383
+ return this.userRepository.save(user);
384
+ }
385
+
386
+ async deleteUser(id: number): Promise<void> {
387
+ const user = await this.findUserById(id);
388
+ await this.userRepository.softDeleteUsers([id]);
389
+ }
390
+
391
+ async searchUsers(
392
+ searchTerm: string,
393
+ pageParams: PageParams
394
+ ): Promise<PagedData<User>> {
395
+ return this.userRepository.searchUsers(searchTerm, pageParams);
396
+ }
397
+
398
+ async getUserStatistics() {
399
+ return this.userRepository.getUserStatistics();
400
+ }
401
+
402
+ async getRecentlyActiveUsers(days: number = 30): Promise<User[]> {
403
+ return this.userRepository.findRecentlyActiveUsers(days);
404
+ }
405
+
406
+ async bulkUpdateUsers(userIds: number[], updateData: Partial<User>): Promise<void> {
407
+ await this.userRepository.bulkUpdateUsers(userIds, updateData);
408
+ }
409
+ }
410
+ ```
411
+
412
+ ### Query Utilities Usage
413
+
414
+ ```typescript
415
+ import { Injectable } from '@nestjs/common';
416
+ import {
417
+ generateDateQuery,
418
+ removeFalseyKeys,
419
+ getSkip,
420
+ getPagingKey
421
+ } from '@onivoro/server-typeorm-mysql';
422
+ import { Repository } from 'typeorm';
423
+ import { InjectRepository } from '@nestjs/typeorm';
424
+ import { Order } from './order.entity';
425
+
426
+ @Injectable()
427
+ export class OrderService {
428
+ constructor(
429
+ @InjectRepository(Order)
430
+ private orderRepository: Repository<Order>
431
+ ) {}
432
+
433
+ async findOrdersByDateRange(
434
+ startDate?: Date,
435
+ endDate?: Date,
436
+ status?: string,
437
+ page: number = 1,
438
+ limit: number = 10
439
+ ) {
440
+ const whereConditions: any = removeFalseyKeys({
441
+ status,
442
+ ...generateDateQuery('createdAt', startDate, endDate)
443
+ });
444
+
445
+ const skip = getSkip(page, limit);
446
+
447
+ const [orders, total] = await this.orderRepository.findAndCount({
448
+ where: whereConditions,
449
+ skip,
450
+ take: limit,
451
+ order: { createdAt: 'DESC' },
452
+ relations: ['user', 'items', 'items.product']
453
+ });
454
+
455
+ return {
456
+ data: orders,
457
+ pagination: {
458
+ page,
459
+ limit,
460
+ total,
461
+ pages: Math.ceil(total / limit),
462
+ key: getPagingKey(page, limit)
463
+ }
464
+ };
465
+ }
466
+
467
+ async getOrderAnalytics(startDate: Date, endDate: Date) {
468
+ const dateQuery = generateDateQuery('createdAt', startDate, endDate);
469
+
470
+ const queryBuilder = this.orderRepository.createQueryBuilder('order')
471
+ .where(dateQuery);
472
+
473
+ const [
474
+ totalOrders,
475
+ totalRevenue,
476
+ averageOrderValue,
477
+ statusBreakdown
478
+ ] = await Promise.all([
479
+ queryBuilder.getCount(),
480
+ queryBuilder
481
+ .select('SUM(order.totalAmount)', 'total')
482
+ .getRawOne()
483
+ .then(result => result.total || 0),
484
+ queryBuilder
485
+ .select('AVG(order.totalAmount)', 'average')
486
+ .getRawOne()
487
+ .then(result => result.average || 0),
488
+ queryBuilder
489
+ .select('order.status', 'status')
490
+ .addSelect('COUNT(*)', 'count')
491
+ .groupBy('order.status')
492
+ .getRawMany()
493
+ ]);
494
+
495
+ return {
496
+ totalOrders,
497
+ totalRevenue: parseFloat(totalRevenue),
498
+ averageOrderValue: parseFloat(averageOrderValue),
499
+ statusBreakdown: statusBreakdown.reduce((acc, item) => {
500
+ acc[item.status] = parseInt(item.count);
501
+ return acc;
502
+ }, {})
503
+ };
504
+ }
505
+ }
506
+ ```
507
+
508
+ ### Database Transactions
509
+
510
+ ```typescript
511
+ import { Injectable } from '@nestjs/common';
512
+ import { DataSource } from 'typeorm';
513
+ import { User } from './user.entity';
514
+ import { Order } from './order.entity';
515
+ import { OrderItem } from './order-item.entity';
516
+
517
+ @Injectable()
518
+ export class OrderTransactionService {
519
+ constructor(private dataSource: DataSource) {}
520
+
521
+ async createOrderWithItems(
522
+ userId: number,
523
+ orderData: Partial<Order>,
524
+ items: Array<{productId: number, quantity: number, unitPrice: number}>
525
+ ): Promise<Order> {
526
+ return this.dataSource.transaction(async manager => {
527
+ // Create the order
528
+ const order = manager.create(Order, {
529
+ ...orderData,
530
+ userId,
531
+ totalAmount: 0 // Will be calculated
532
+ });
533
+
534
+ const savedOrder = await manager.save(order);
535
+
536
+ // Create order items
537
+ let totalAmount = 0;
538
+ const orderItems = [];
539
+
540
+ for (const itemData of items) {
541
+ const totalPrice = itemData.quantity * itemData.unitPrice;
542
+ totalAmount += totalPrice;
543
+
544
+ const orderItem = manager.create(OrderItem, {
545
+ orderId: savedOrder.id,
546
+ productId: itemData.productId,
547
+ quantity: itemData.quantity,
548
+ unitPrice: itemData.unitPrice,
549
+ totalPrice
550
+ });
551
+
552
+ orderItems.push(await manager.save(orderItem));
553
+ }
554
+
555
+ // Update order total
556
+ savedOrder.totalAmount = totalAmount;
557
+ await manager.save(savedOrder);
558
+
559
+ // Update user's last order date
560
+ await manager.update(User, userId, {
561
+ updatedAt: new Date()
562
+ });
563
+
564
+ return savedOrder;
565
+ });
566
+ }
567
+
568
+ async transferOrderToNewUser(
569
+ orderId: number,
570
+ newUserId: number
571
+ ): Promise<void> {
572
+ await this.dataSource.transaction(async manager => {
573
+ // Update order
574
+ await manager.update(Order, orderId, {
575
+ userId: newUserId,
576
+ updatedAt: new Date()
577
+ });
578
+
579
+ // Log the transfer
580
+ await manager.query(
581
+ 'INSERT INTO order_transfers (order_id, new_user_id, transferred_at) VALUES (?, ?, ?)',
582
+ [orderId, newUserId, new Date()]
583
+ );
584
+ });
585
+ }
586
+ }
587
+ ```
588
+
589
+ ### Custom Query Builder
590
+
591
+ ```typescript
592
+ import { Injectable } from '@nestjs/common';
593
+ import { Repository, SelectQueryBuilder } from 'typeorm';
594
+ import { InjectRepository } from '@nestjs/typeorm';
595
+ import { Order } from './order.entity';
596
+
597
+ @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
+ }
635
+
636
+ if (filters.minAmount !== undefined) {
637
+ query = query.andWhere('order.totalAmount >= :minAmount', {
638
+ minAmount: filters.minAmount
639
+ });
640
+ }
641
+
642
+ if (filters.maxAmount !== undefined) {
643
+ query = query.andWhere('order.totalAmount <= :maxAmount', {
644
+ maxAmount: filters.maxAmount
645
+ });
646
+ }
647
+
648
+ if (filters.startDate) {
649
+ query = query.andWhere('order.createdAt >= :startDate', {
650
+ startDate: filters.startDate
651
+ });
652
+ }
653
+
654
+ if (filters.endDate) {
655
+ query = query.andWhere('order.createdAt <= :endDate', {
656
+ endDate: filters.endDate
657
+ });
658
+ }
659
+
660
+ query = query.orderBy('order.createdAt', 'DESC');
661
+
662
+ if (filters.limit) {
663
+ query = query.take(filters.limit);
664
+ }
665
+
666
+ if (filters.offset) {
667
+ query = query.skip(filters.offset);
668
+ }
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
+ }
706
+ }
707
+ ```
708
+
709
+ ## API Reference
710
+
711
+ ### Repository Classes
712
+
713
+ #### TypeOrmRepository<T>
714
+
715
+ Base repository class with enhanced functionality:
716
+
717
+ ```typescript
718
+ export class TypeOrmRepository<T> extends Repository<T> {
719
+ constructor(entity: EntityTarget<T>)
720
+
721
+ // Enhanced methods with better error handling and utilities
722
+ }
723
+ ```
724
+
725
+ #### TypeOrmPagingRepository<T>
726
+
727
+ Repository with built-in pagination support:
728
+
729
+ ```typescript
730
+ export class TypeOrmPagingRepository<T> extends TypeOrmRepository<T> {
731
+ async findWithPaging(
732
+ options: FindManyOptions<T>,
733
+ pageParams: PageParams
734
+ ): Promise<PagedData<T>>
735
+ }
736
+ ```
737
+
738
+ ### Decorators
739
+
740
+ #### @Table(name?: string)
741
+
742
+ Enhanced table decorator:
743
+
744
+ ```typescript
745
+ @Table('table_name')
746
+ export class Entity {}
747
+ ```
748
+
749
+ #### @PrimaryTableColumn(options?)
750
+
751
+ Primary key column decorator:
752
+
753
+ ```typescript
754
+ @PrimaryTableColumn()
755
+ id: number;
756
+ ```
757
+
758
+ #### @TableColumn(options)
759
+
760
+ Standard column decorator:
761
+
762
+ ```typescript
763
+ @TableColumn({ type: 'varchar', length: 255 })
764
+ name: string;
765
+ ```
766
+
767
+ #### @NullableTableColumn(options)
768
+
769
+ Nullable column decorator:
770
+
771
+ ```typescript
772
+ @NullableTableColumn({ type: 'datetime' })
773
+ deletedAt?: Date;
774
+ ```
775
+
776
+ ### Utility Functions
777
+
778
+ #### dataSourceConfigFactory(options)
779
+
780
+ Create data source configuration:
781
+
782
+ ```typescript
783
+ function dataSourceConfigFactory(options: DataSourceOptions): DataSourceOptions
784
+ ```
785
+
786
+ #### generateDateQuery(field, startDate?, endDate?)
787
+
788
+ Generate date range query conditions:
789
+
790
+ ```typescript
791
+ function generateDateQuery(
792
+ field: string,
793
+ startDate?: Date,
794
+ endDate?: Date
795
+ ): Record<string, any>
796
+ ```
797
+
798
+ #### removeFalseyKeys(object)
799
+
800
+ Remove falsy values from object:
801
+
802
+ ```typescript
803
+ function removeFalseyKeys<T>(obj: T): Partial<T>
804
+ ```
805
+
806
+ ### Type Definitions
807
+
808
+ #### PageParams
809
+
810
+ Pagination parameters:
811
+
812
+ ```typescript
813
+ interface PageParams {
814
+ page: number;
815
+ limit: number;
816
+ }
817
+ ```
818
+
819
+ #### PagedData<T>
820
+
821
+ Paginated response data:
822
+
823
+ ```typescript
824
+ interface PagedData<T> {
825
+ data: T[];
826
+ pagination: {
827
+ page: number;
828
+ limit: number;
829
+ total: number;
830
+ pages: number;
831
+ };
832
+ }
833
+ ```
834
+
835
+ ## Best Practices
836
+
837
+ 1. **Repository Pattern**: Use custom repositories for complex queries
838
+ 2. **Transactions**: Use transactions for multi-table operations
839
+ 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
843
+ 7. **Type Safety**: Leverage TypeScript for type-safe database operations
844
+ 8. **Connection Pooling**: Configure appropriate connection pool settings
845
+
846
+ ## Testing
847
+
848
+ ```typescript
849
+ import { Test } from '@nestjs/testing';
850
+ import { getRepositoryToken } from '@nestjs/typeorm';
851
+ import { Repository } from 'typeorm';
852
+ import { User } from './user.entity';
853
+ import { UserService } from './user.service';
854
+
855
+ describe('UserService', () => {
856
+ let service: UserService;
857
+ let repository: Repository<User>;
858
+
859
+ beforeEach(async () => {
860
+ const module = await Test.createTestingModule({
861
+ providers: [
862
+ UserService,
863
+ {
864
+ provide: getRepositoryToken(User),
865
+ useClass: Repository,
866
+ },
867
+ ],
868
+ }).compile();
869
+
870
+ service = module.get<UserService>(UserService);
871
+ repository = module.get<Repository<User>>(getRepositoryToken(User));
872
+ });
873
+
874
+ it('should create a user', async () => {
875
+ const userData = {
876
+ email: 'test@example.com',
877
+ firstName: 'John',
878
+ lastName: 'Doe'
879
+ };
880
+
881
+ jest.spyOn(repository, 'create').mockReturnValue(userData as User);
882
+ jest.spyOn(repository, 'save').mockResolvedValue(userData as User);
883
+
884
+ const result = await service.createUser(userData);
885
+ expect(result).toEqual(userData);
886
+ });
887
+ });
888
+ ```
889
+
890
+ ## License
891
+
892
+ This library is part of the Onivoro monorepo ecosystem.