@onivoro/server-typeorm-mysql 22.0.4 → 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 (124) hide show
  1. package/README.md +1002 -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,1002 @@
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 { EntityManager } from 'typeorm';
96
+ import { User } from './user.entity';
97
+
98
+ @Injectable()
99
+ export class UserRepository extends TypeOrmPagingRepository<User> {
100
+ constructor(entityManager: EntityManager) {
101
+ super(User, entityManager);
102
+ }
103
+
104
+ async findByEmail(email: string): Promise<User | null> {
105
+ return this.getOne({ where: { email } });
106
+ }
107
+
108
+ async findActiveUsers(): Promise<User[]> {
109
+ return this.getMany({ where: { isActive: true } });
110
+ }
111
+
112
+ async findUsersWithPagination(page: number, limit: number) {
113
+ return this.findWithPaging(
114
+ { where: { isActive: true } },
115
+ { page, limit }
116
+ );
117
+ }
118
+ }
119
+ ```
120
+
121
+ ## Configuration
122
+
123
+ ### Data Source Configuration
124
+
125
+ ```typescript
126
+ import { dataSourceConfigFactory } from '@onivoro/server-typeorm-mysql';
127
+
128
+ const config = dataSourceConfigFactory({
129
+ host: process.env.DB_HOST,
130
+ port: parseInt(process.env.DB_PORT),
131
+ username: process.env.DB_USERNAME,
132
+ password: process.env.DB_PASSWORD,
133
+ database: process.env.DB_DATABASE,
134
+ entities: [User, Product, Order],
135
+ migrations: ['src/migrations/*.ts'],
136
+ synchronize: false,
137
+ logging: process.env.NODE_ENV === 'development',
138
+ ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
139
+ });
140
+ ```
141
+
142
+ ### Dynamic Module Configuration
143
+
144
+ ```typescript
145
+ import { Module } from '@nestjs/common';
146
+ import { ServerTypeormMysqlModule } from '@onivoro/server-typeorm-mysql';
147
+ import { ConfigService } from '@nestjs/config';
148
+
149
+ @Module({
150
+ imports: [
151
+ ServerTypeormMysqlModule.forRootAsync({
152
+ useFactory: (configService: ConfigService) => ({
153
+ host: configService.get('DATABASE_HOST'),
154
+ port: configService.get('DATABASE_PORT'),
155
+ username: configService.get('DATABASE_USERNAME'),
156
+ password: configService.get('DATABASE_PASSWORD'),
157
+ database: configService.get('DATABASE_NAME'),
158
+ entities: [__dirname + '/**/*.entity{.ts,.js}'],
159
+ migrations: [__dirname + '/migrations/*{.ts,.js}'],
160
+ synchronize: configService.get('NODE_ENV') === 'development',
161
+ logging: configService.get('DATABASE_LOGGING') === 'true'
162
+ }),
163
+ inject: [ConfigService]
164
+ })
165
+ ],
166
+ })
167
+ export class DatabaseModule {}
168
+ ```
169
+
170
+ ## Usage Examples
171
+
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
234
+
235
+ ```typescript
236
+ import { Injectable } from '@nestjs/common';
237
+ import { TypeOrmPagingRepository, PageParams, PagedData } from '@onivoro/server-typeorm-mysql';
238
+ import { EntityManager, Like, Between } from 'typeorm';
239
+ import { User } from './user.entity';
240
+
241
+ @Injectable()
242
+ export class AdvancedUserRepository extends TypeOrmPagingRepository<User> {
243
+ constructor(entityManager: EntityManager) {
244
+ super(User, entityManager);
245
+ }
246
+
247
+ async searchUsers(
248
+ searchTerm: string,
249
+ pageParams: PageParams
250
+ ): Promise<PagedData<User>> {
251
+ const whereConditions = this.buildWhereILike({
252
+ firstName: searchTerm,
253
+ lastName: searchTerm,
254
+ email: searchTerm
255
+ });
256
+
257
+ return this.findWithPaging(
258
+ {
259
+ where: [
260
+ { firstName: Like(`%${searchTerm}%`) },
261
+ { lastName: Like(`%${searchTerm}%`) },
262
+ { email: Like(`%${searchTerm}%`) }
263
+ ],
264
+ order: { createdAt: 'DESC' }
265
+ },
266
+ pageParams
267
+ );
268
+ }
269
+
270
+ async findUsersByDateRange(
271
+ startDate: Date,
272
+ endDate: Date,
273
+ pageParams: PageParams
274
+ ): Promise<PagedData<User>> {
275
+ return this.findWithPaging(
276
+ {
277
+ where: {
278
+ createdAt: Between(startDate, endDate)
279
+ },
280
+ order: { createdAt: 'DESC' }
281
+ },
282
+ pageParams
283
+ );
284
+ }
285
+
286
+ async findRecentlyActiveUsers(days: number = 30): Promise<User[]> {
287
+ const cutoffDate = new Date();
288
+ cutoffDate.setDate(cutoffDate.getDate() - days);
289
+
290
+ return this.getMany({
291
+ where: {
292
+ lastLoginAt: Between(cutoffDate, new Date())
293
+ },
294
+ order: { lastLoginAt: 'DESC' }
295
+ });
296
+ }
297
+
298
+ async getUserStatistics(): Promise<{
299
+ total: number;
300
+ active: number;
301
+ inactive: number;
302
+ recentlyRegistered: number;
303
+ }> {
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
+ });
316
+
317
+ return {
318
+ total: totalCount,
319
+ active: activeCount,
320
+ inactive: totalCount - activeCount,
321
+ recentlyRegistered: recentCount
322
+ };
323
+ }
324
+
325
+ async bulkUpdateUsers(
326
+ userIds: number[],
327
+ updateData: Partial<User>
328
+ ): Promise<void> {
329
+ for (const id of userIds) {
330
+ await this.patch({ id }, updateData);
331
+ }
332
+ }
333
+
334
+ async softDeleteUsers(userIds: number[]): Promise<void> {
335
+ for (const id of userIds) {
336
+ await this.softDelete({ id });
337
+ }
338
+ }
339
+ }
340
+ ```
341
+
342
+ ### Complex Entity Relationships
343
+
344
+ ```typescript
345
+ import {
346
+ Table,
347
+ PrimaryTableColumn,
348
+ TableColumn,
349
+ NullableTableColumn,
350
+ ManyToOneRelationOptions
351
+ } from '@onivoro/server-typeorm-mysql';
352
+ import { Entity, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
353
+
354
+ @Entity()
355
+ @Table('orders')
356
+ export class Order {
357
+ @PrimaryTableColumn()
358
+ id: number;
359
+
360
+ @TableColumn({ type: 'varchar', length: 50 })
361
+ orderNumber: string;
362
+
363
+ @TableColumn({ type: 'decimal', precision: 10, scale: 2 })
364
+ totalAmount: number;
365
+
366
+ @TableColumn({ type: 'enum', enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'] })
367
+ status: string;
368
+
369
+ @TableColumn({ type: 'int' })
370
+ userId: number;
371
+
372
+ @TableColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
373
+ createdAt: Date;
374
+
375
+ @NullableTableColumn({ type: 'timestamp' })
376
+ shippedAt?: Date;
377
+
378
+ @NullableTableColumn({ type: 'timestamp' })
379
+ deliveredAt?: Date;
380
+
381
+ // Relationships
382
+ @ManyToOne(() => User, user => user.orders, ManyToOneRelationOptions)
383
+ @JoinColumn({ name: 'userId' })
384
+ user: User;
385
+
386
+ @OneToMany(() => OrderItem, orderItem => orderItem.order)
387
+ items: OrderItem[];
388
+ }
389
+
390
+ @Entity()
391
+ @Table('order_items')
392
+ export class OrderItem {
393
+ @PrimaryTableColumn()
394
+ id: number;
395
+
396
+ @TableColumn({ type: 'int' })
397
+ orderId: number;
398
+
399
+ @TableColumn({ type: 'int' })
400
+ productId: number;
401
+
402
+ @TableColumn({ type: 'int' })
403
+ quantity: number;
404
+
405
+ @TableColumn({ type: 'decimal', precision: 10, scale: 2 })
406
+ unitPrice: number;
407
+
408
+ @TableColumn({ type: 'decimal', precision: 10, scale: 2 })
409
+ totalPrice: number;
410
+
411
+ @ManyToOne(() => Order, order => order.items, ManyToOneRelationOptions)
412
+ @JoinColumn({ name: 'orderId' })
413
+ order: Order;
414
+
415
+ @ManyToOne(() => Product, product => product.orderItems, ManyToOneRelationOptions)
416
+ @JoinColumn({ name: 'productId' })
417
+ product: Product;
418
+ }
419
+ ```
420
+
421
+ ### Service Layer with Repository
422
+
423
+ ```typescript
424
+ import { Injectable, NotFoundException } from '@nestjs/common';
425
+ import { AdvancedUserRepository } from './user.repository';
426
+ import { User } from './user.entity';
427
+ import { PageParams, PagedData } from '@onivoro/server-typeorm-mysql';
428
+
429
+ @Injectable()
430
+ export class UserService {
431
+ constructor(
432
+ private userRepository: AdvancedUserRepository
433
+ ) {}
434
+
435
+ async createUser(userData: Partial<User>): Promise<User> {
436
+ return this.userRepository.postOne(userData);
437
+ }
438
+
439
+ async findUserById(id: number): Promise<User> {
440
+ const user = await this.userRepository.getOne({ where: { id } });
441
+ if (!user) {
442
+ throw new NotFoundException(`User with ID ${id} not found`);
443
+ }
444
+ return user;
445
+ }
446
+
447
+ async updateUser(id: number, updateData: Partial<User>): Promise<void> {
448
+ const user = await this.findUserById(id);
449
+ await this.userRepository.patch({ id }, updateData);
450
+ }
451
+
452
+ async deleteUser(id: number): Promise<void> {
453
+ const user = await this.findUserById(id);
454
+ await this.userRepository.softDelete({ id });
455
+ }
456
+
457
+ async searchUsers(
458
+ searchTerm: string,
459
+ pageParams: PageParams
460
+ ): Promise<PagedData<User>> {
461
+ return this.userRepository.searchUsers(searchTerm, pageParams);
462
+ }
463
+
464
+ async getUserStatistics() {
465
+ return this.userRepository.getUserStatistics();
466
+ }
467
+
468
+ async getRecentlyActiveUsers(days: number = 30): Promise<User[]> {
469
+ return this.userRepository.findRecentlyActiveUsers(days);
470
+ }
471
+
472
+ async bulkUpdateUsers(userIds: number[], updateData: Partial<User>): Promise<void> {
473
+ await this.userRepository.bulkUpdateUsers(userIds, updateData);
474
+ }
475
+ }
476
+ ```
477
+
478
+ ### Query Utilities Usage
479
+
480
+ ```typescript
481
+ import { Injectable } from '@nestjs/common';
482
+ import {
483
+ generateDateQuery,
484
+ removeFalseyKeys,
485
+ getSkip,
486
+ getPagingKey,
487
+ TypeOrmRepository
488
+ } from '@onivoro/server-typeorm-mysql';
489
+ import { EntityManager } from 'typeorm';
490
+ import { Order } from './order.entity';
491
+
492
+ @Injectable()
493
+ export class OrderService extends TypeOrmRepository<Order> {
494
+ constructor(entityManager: EntityManager) {
495
+ super(Order, entityManager);
496
+ }
497
+
498
+ async findOrdersByDateRange(
499
+ startDate?: Date,
500
+ endDate?: Date,
501
+ status?: string,
502
+ page: number = 1,
503
+ limit: number = 10
504
+ ) {
505
+ const whereConditions: any = removeFalseyKeys({
506
+ status,
507
+ ...generateDateQuery('createdAt', startDate, endDate)
508
+ });
509
+
510
+ const skip = getSkip(page, limit);
511
+
512
+ const [orders, total] = await this.getManyAndCount({
513
+ where: whereConditions,
514
+ skip,
515
+ take: limit,
516
+ order: { createdAt: 'DESC' },
517
+ relations: ['user', 'items', 'items.product']
518
+ });
519
+
520
+ return {
521
+ data: orders,
522
+ pagination: {
523
+ page,
524
+ limit,
525
+ total,
526
+ pages: Math.ceil(total / limit),
527
+ key: getPagingKey(page, limit)
528
+ }
529
+ };
530
+ }
531
+
532
+ async getOrderAnalytics(startDate: Date, endDate: Date) {
533
+ const dateQuery = generateDateQuery('createdAt', startDate, endDate);
534
+
535
+ const queryBuilder = this.repo.createQueryBuilder('order')
536
+ .where(dateQuery);
537
+
538
+ const [
539
+ totalOrders,
540
+ totalRevenue,
541
+ averageOrderValue,
542
+ statusBreakdown
543
+ ] = await Promise.all([
544
+ queryBuilder.getCount(),
545
+ queryBuilder
546
+ .select('SUM(order.totalAmount)', 'total')
547
+ .getRawOne()
548
+ .then(result => result.total || 0),
549
+ queryBuilder
550
+ .select('AVG(order.totalAmount)', 'average')
551
+ .getRawOne()
552
+ .then(result => result.average || 0),
553
+ queryBuilder
554
+ .select('order.status', 'status')
555
+ .addSelect('COUNT(*)', 'count')
556
+ .groupBy('order.status')
557
+ .getRawMany()
558
+ ]);
559
+
560
+ return {
561
+ totalOrders,
562
+ totalRevenue: parseFloat(totalRevenue),
563
+ averageOrderValue: parseFloat(averageOrderValue),
564
+ statusBreakdown: statusBreakdown.reduce((acc, item) => {
565
+ acc[item.status] = parseInt(item.count);
566
+ return acc;
567
+ }, {})
568
+ };
569
+ }
570
+ }
571
+ ```
572
+
573
+ ### Database Transactions
574
+
575
+ ```typescript
576
+ import { Injectable } from '@nestjs/common';
577
+ import { EntityManager } from 'typeorm';
578
+ import { TypeOrmRepository } from '@onivoro/server-typeorm-mysql';
579
+ import { User } from './user.entity';
580
+ import { Order } from './order.entity';
581
+ import { OrderItem } from './order-item.entity';
582
+
583
+ @Injectable()
584
+ export class OrderTransactionService {
585
+ constructor(private entityManager: EntityManager) {}
586
+
587
+ async createOrderWithItems(
588
+ userId: number,
589
+ orderData: Partial<Order>,
590
+ items: Array<{productId: number, quantity: number, unitPrice: number}>
591
+ ): Promise<Order> {
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
+
597
+ // Create the order
598
+ const order = await orderRepo.postOne({
599
+ ...orderData,
600
+ userId,
601
+ totalAmount: 0 // Will be calculated
602
+ });
603
+
604
+ // Create order items
605
+ let totalAmount = 0;
606
+ const orderItems = [];
607
+
608
+ for (const itemData of items) {
609
+ const totalPrice = itemData.quantity * itemData.unitPrice;
610
+ totalAmount += totalPrice;
611
+
612
+ const orderItem = await orderItemRepo.postOne({
613
+ orderId: order.id,
614
+ productId: itemData.productId,
615
+ quantity: itemData.quantity,
616
+ unitPrice: itemData.unitPrice,
617
+ totalPrice
618
+ });
619
+
620
+ orderItems.push(orderItem);
621
+ }
622
+
623
+ // Update order total
624
+ await orderRepo.patch({ id: order.id }, { totalAmount });
625
+
626
+ // Update user's last order date
627
+ await userRepo.patch({ id: userId }, {
628
+ updatedAt: new Date()
629
+ });
630
+
631
+ return order;
632
+ });
633
+ }
634
+
635
+ async transferOrderToNewUser(
636
+ orderId: number,
637
+ newUserId: number
638
+ ): Promise<void> {
639
+ await this.entityManager.transaction(async transactionalEntityManager => {
640
+ const orderRepo = new TypeOrmRepository<Order>(Order, transactionalEntityManager);
641
+
642
+ // Update order
643
+ await orderRepo.patch({ id: orderId }, {
644
+ userId: newUserId,
645
+ updatedAt: new Date()
646
+ });
647
+
648
+ // Log the transfer using raw query
649
+ await transactionalEntityManager.query(
650
+ 'INSERT INTO order_transfers (order_id, new_user_id, transferred_at) VALUES (?, ?, ?)',
651
+ [orderId, newUserId, new Date()]
652
+ );
653
+ });
654
+ }
655
+ }
656
+ ```
657
+
658
+ ### Query Streaming
659
+
660
+ ```typescript
661
+ import { Injectable } from '@nestjs/common';
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';
666
+
667
+ @Injectable()
668
+ export class UserStreamingService extends TypeOrmRepository<User> {
669
+ constructor(entityManager: EntityManager) {
670
+ super(User, entityManager);
671
+ }
672
+
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
+ });
697
+
698
+ if (error) {
699
+ throw new Error(`Failed to start streaming: ${error.message}`);
700
+ }
701
+ }
702
+
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
+ });
718
+
719
+ if (error) {
720
+ throw new Error(`Failed to process dataset: ${error.message}`);
721
+ }
722
+ }
723
+
724
+ private async processUserRecord(user: User): Promise<void> {
725
+ // Your custom processing logic here
726
+ console.log(`Processing user: ${user.email}`);
727
+ }
728
+
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
+ });
743
+
744
+ if (error) {
745
+ console.error('Stream failed:', error);
746
+ }
747
+ }
748
+ }
749
+ ```
750
+
751
+ ## API Reference
752
+
753
+ ### Repository Classes
754
+
755
+ #### TypeOrmRepository<T>
756
+
757
+ Base repository class with enhanced functionality:
758
+
759
+ ```typescript
760
+ export class TypeOrmRepository<T> {
761
+ constructor(entityType: any, entityManager: EntityManager)
762
+
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>
786
+ }
787
+ ```
788
+
789
+ #### TypeOrmPagingRepository<T>
790
+
791
+ Repository with built-in pagination support:
792
+
793
+ ```typescript
794
+ export class TypeOrmPagingRepository<T> extends TypeOrmRepository<T> {
795
+ async findWithPaging(
796
+ options: FindManyOptions<T>,
797
+ pageParams: PageParams
798
+ ): Promise<PagedData<T>>
799
+ }
800
+ ```
801
+
802
+ ### Decorators
803
+
804
+ #### @Table(name?: string)
805
+
806
+ Enhanced table decorator:
807
+
808
+ ```typescript
809
+ @Table('table_name')
810
+ export class Entity {}
811
+ ```
812
+
813
+ #### @PrimaryTableColumn(options?)
814
+
815
+ Primary key column decorator:
816
+
817
+ ```typescript
818
+ @PrimaryTableColumn()
819
+ id: number;
820
+ ```
821
+
822
+ #### @TableColumn(options)
823
+
824
+ Standard column decorator:
825
+
826
+ ```typescript
827
+ @TableColumn({ type: 'varchar', length: 255 })
828
+ name: string;
829
+ ```
830
+
831
+ #### @NullableTableColumn(options)
832
+
833
+ Nullable column decorator:
834
+
835
+ ```typescript
836
+ @NullableTableColumn({ type: 'datetime' })
837
+ deletedAt?: Date;
838
+ ```
839
+
840
+ ### Utility Functions
841
+
842
+ #### dataSourceConfigFactory(options)
843
+
844
+ Create data source configuration:
845
+
846
+ ```typescript
847
+ function dataSourceConfigFactory(options: DataSourceOptions): DataSourceOptions
848
+ ```
849
+
850
+ #### generateDateQuery(field, startDate?, endDate?)
851
+
852
+ Generate date range query conditions:
853
+
854
+ ```typescript
855
+ function generateDateQuery(
856
+ field: string,
857
+ startDate?: Date,
858
+ endDate?: Date
859
+ ): Record<string, any>
860
+ ```
861
+
862
+ #### removeFalseyKeys(object)
863
+
864
+ Remove falsy values from object:
865
+
866
+ ```typescript
867
+ function removeFalseyKeys<T>(obj: T): Partial<T>
868
+ ```
869
+
870
+ ### Type Definitions
871
+
872
+ #### PageParams
873
+
874
+ Pagination parameters:
875
+
876
+ ```typescript
877
+ interface PageParams {
878
+ page: number;
879
+ limit: number;
880
+ }
881
+ ```
882
+
883
+ #### PagedData<T>
884
+
885
+ Paginated response data:
886
+
887
+ ```typescript
888
+ interface PagedData<T> {
889
+ data: T[];
890
+ pagination: {
891
+ page: number;
892
+ limit: number;
893
+ total: number;
894
+ pages: number;
895
+ };
896
+ }
897
+ ```
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
+
912
+ ## Best Practices
913
+
914
+ 1. **Repository Pattern**: Use custom repositories extending TypeOrmRepository for domain-specific operations
915
+ 2. **Transactions**: Use `forTransaction()` method for multi-table operations
916
+ 3. **Indexing**: Add proper indexes for frequently queried columns
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
920
+ 7. **Type Safety**: Leverage TypeScript for type-safe database operations
921
+ 8. **Connection Pooling**: Configure appropriate connection pool settings
922
+
923
+ ## Testing
924
+
925
+ ```typescript
926
+ import { Test } from '@nestjs/testing';
927
+ import { EntityManager } from 'typeorm';
928
+ import { User } from './user.entity';
929
+ import { UserService } from './user.service';
930
+ import { AdvancedUserRepository } from './user.repository';
931
+
932
+ describe('UserService', () => {
933
+ let service: UserService;
934
+ let repository: AdvancedUserRepository;
935
+ let entityManager: EntityManager;
936
+
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
+
957
+ const module = await Test.createTestingModule({
958
+ providers: [
959
+ UserService,
960
+ {
961
+ provide: AdvancedUserRepository,
962
+ useFactory: () => new AdvancedUserRepository(mockEntityManager as any)
963
+ },
964
+ {
965
+ provide: EntityManager,
966
+ useValue: mockEntityManager
967
+ }
968
+ ],
969
+ }).compile();
970
+
971
+ service = module.get<UserService>(UserService);
972
+ repository = module.get<AdvancedUserRepository>(AdvancedUserRepository);
973
+ entityManager = module.get<EntityManager>(EntityManager);
974
+ });
975
+
976
+ it('should create a user', async () => {
977
+ const userData = {
978
+ email: 'test@example.com',
979
+ firstName: 'John',
980
+ lastName: 'Doe'
981
+ };
982
+
983
+ const createdUser = { id: 1, ...userData };
984
+ jest.spyOn(repository, 'postOne').mockResolvedValue(createdUser as User);
985
+
986
+ const result = await service.createUser(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);
996
+ });
997
+ });
998
+ ```
999
+
1000
+ ## License
1001
+
1002
+ This library is part of the Onivoro monorepo ecosystem.