@onivoro/server-typeorm-postgres 22.0.13 → 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 (178) hide show
  1. package/README.md +728 -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/column-migration-base.class.ts +17 -0
  9. package/src/lib/classes/columns-migration-base.class.ts +17 -0
  10. package/src/lib/classes/drop-column-migration-base.class.ts +17 -0
  11. package/src/lib/classes/drop-table-migration-base.class.ts +17 -0
  12. package/src/lib/classes/index-migration-base.class.ts +17 -0
  13. package/src/lib/classes/redshift-repository.class.ts +146 -0
  14. package/src/lib/classes/sql-writer.class.spec.ts +20 -0
  15. package/{dist/esm/lib/classes/sql-writer.class.js → src/lib/classes/sql-writer.class.ts} +29 -16
  16. package/src/lib/classes/table-migration-base.class.ts +17 -0
  17. package/src/lib/classes/type-orm-paging-repository.class.ts +22 -0
  18. package/src/lib/classes/type-orm-repository.class.ts +299 -0
  19. package/src/lib/constants/many-to-one-relation-options.constant.ts +3 -0
  20. package/src/lib/decorators/nullable-table-column.decorator.ts +9 -0
  21. package/src/lib/decorators/primary-table-column.decorator.ts +9 -0
  22. package/src/lib/decorators/table-column.decorator.ts +9 -0
  23. package/src/lib/decorators/table.decorator.ts +8 -0
  24. package/src/lib/functions/data-source-config-factory.function.ts +34 -0
  25. package/src/lib/functions/data-source-factory.function.ts +10 -0
  26. package/src/lib/functions/generate-date-query.function.ts +20 -0
  27. package/src/lib/functions/get-api-type-from-column.function.ts +14 -0
  28. package/src/lib/functions/get-paging-key.function.ts +3 -0
  29. package/src/lib/functions/get-skip.function.ts +3 -0
  30. package/src/lib/functions/remove-falsey-keys.function.ts +8 -0
  31. package/src/lib/server-typeorm-postgres.module.ts +44 -0
  32. package/src/lib/types/data-source-options.interface.ts +11 -0
  33. package/{dist/cjs/lib/types/entity-provider.interface.d.ts → src/lib/types/entity-provider.interface.ts} +2 -2
  34. package/{dist/esm/lib/types/page-params.interface.d.ts → src/lib/types/page-params.interface.ts} +2 -0
  35. package/src/lib/types/table-meta.type.ts +4 -0
  36. package/tsconfig.json +17 -0
  37. package/tsconfig.lib.json +8 -0
  38. package/tsconfig.spec.json +21 -0
  39. package/dist/cjs/index.js +0 -44
  40. package/dist/cjs/lib/classes/column-migration-base.class.d.ts +0 -8
  41. package/dist/cjs/lib/classes/column-migration-base.class.js +0 -19
  42. package/dist/cjs/lib/classes/columns-migration-base.class.d.ts +0 -8
  43. package/dist/cjs/lib/classes/columns-migration-base.class.js +0 -19
  44. package/dist/cjs/lib/classes/drop-column-migration-base.class.d.ts +0 -8
  45. package/dist/cjs/lib/classes/drop-column-migration-base.class.js +0 -19
  46. package/dist/cjs/lib/classes/drop-table-migration-base.class.d.ts +0 -8
  47. package/dist/cjs/lib/classes/drop-table-migration-base.class.js +0 -19
  48. package/dist/cjs/lib/classes/index-migration-base.class.d.ts +0 -8
  49. package/dist/cjs/lib/classes/index-migration-base.class.js +0 -19
  50. package/dist/cjs/lib/classes/redshift-repository.class.d.ts +0 -29
  51. package/dist/cjs/lib/classes/redshift-repository.class.js +0 -129
  52. package/dist/cjs/lib/classes/sql-writer.class.d.ts +0 -14
  53. package/dist/cjs/lib/classes/sql-writer.class.js +0 -53
  54. package/dist/cjs/lib/classes/table-migration-base.class.d.ts +0 -8
  55. package/dist/cjs/lib/classes/table-migration-base.class.js +0 -19
  56. package/dist/cjs/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
  57. package/dist/cjs/lib/classes/type-orm-paging-repository.class.js +0 -16
  58. package/dist/cjs/lib/classes/type-orm-repository.class.d.ts +0 -62
  59. package/dist/cjs/lib/classes/type-orm-repository.class.js +0 -202
  60. package/dist/cjs/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
  61. package/dist/cjs/lib/constants/many-to-one-relation-options.constant.js +0 -4
  62. package/dist/cjs/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
  63. package/dist/cjs/lib/decorators/nullable-table-column.decorator.js +0 -12
  64. package/dist/cjs/lib/decorators/primary-table-column.decorator.d.ts +0 -2
  65. package/dist/cjs/lib/decorators/primary-table-column.decorator.js +0 -12
  66. package/dist/cjs/lib/decorators/table-column.decorator.d.ts +0 -2
  67. package/dist/cjs/lib/decorators/table-column.decorator.js +0 -12
  68. package/dist/cjs/lib/decorators/table.decorator.d.ts +0 -3
  69. package/dist/cjs/lib/decorators/table.decorator.js +0 -11
  70. package/dist/cjs/lib/functions/data-source-config-factory.function.d.ts +0 -3
  71. package/dist/cjs/lib/functions/data-source-config-factory.function.js +0 -25
  72. package/dist/cjs/lib/functions/data-source-factory.function.d.ts +0 -3
  73. package/dist/cjs/lib/functions/data-source-factory.function.js +0 -7
  74. package/dist/cjs/lib/functions/generate-date-query.function.d.ts +0 -2
  75. package/dist/cjs/lib/functions/generate-date-query.function.js +0 -16
  76. package/dist/cjs/lib/functions/get-api-type-from-column.function.d.ts +0 -2
  77. package/dist/cjs/lib/functions/get-api-type-from-column.function.js +0 -14
  78. package/dist/cjs/lib/functions/get-paging-key.function.d.ts +0 -1
  79. package/dist/cjs/lib/functions/get-paging-key.function.js +0 -6
  80. package/dist/cjs/lib/functions/get-skip.function.d.ts +0 -1
  81. package/dist/cjs/lib/functions/get-skip.function.js +0 -6
  82. package/dist/cjs/lib/functions/remove-falsey-keys.function.d.ts +0 -1
  83. package/dist/cjs/lib/functions/remove-falsey-keys.function.js +0 -11
  84. package/dist/cjs/lib/server-typeorm-postgres.module.d.ts +0 -5
  85. package/dist/cjs/lib/server-typeorm-postgres.module.js +0 -49
  86. package/dist/cjs/lib/types/data-source-options.interface.d.ts +0 -11
  87. package/dist/cjs/lib/types/data-source-options.interface.js +0 -2
  88. package/dist/cjs/lib/types/entity-provider.interface.js +0 -2
  89. package/dist/cjs/lib/types/page-params.interface.d.ts +0 -4
  90. package/dist/cjs/lib/types/page-params.interface.js +0 -2
  91. package/dist/cjs/lib/types/paged-data.interface.js +0 -2
  92. package/dist/cjs/lib/types/table-meta.type.d.ts +0 -9
  93. package/dist/cjs/lib/types/table-meta.type.js +0 -2
  94. package/dist/esm/index.js +0 -44
  95. package/dist/esm/lib/classes/column-migration-base.class.d.ts +0 -8
  96. package/dist/esm/lib/classes/column-migration-base.class.js +0 -19
  97. package/dist/esm/lib/classes/columns-migration-base.class.d.ts +0 -8
  98. package/dist/esm/lib/classes/columns-migration-base.class.js +0 -19
  99. package/dist/esm/lib/classes/drop-column-migration-base.class.d.ts +0 -8
  100. package/dist/esm/lib/classes/drop-column-migration-base.class.js +0 -19
  101. package/dist/esm/lib/classes/drop-table-migration-base.class.d.ts +0 -8
  102. package/dist/esm/lib/classes/drop-table-migration-base.class.js +0 -19
  103. package/dist/esm/lib/classes/index-migration-base.class.d.ts +0 -8
  104. package/dist/esm/lib/classes/index-migration-base.class.js +0 -19
  105. package/dist/esm/lib/classes/redshift-repository.class.d.ts +0 -29
  106. package/dist/esm/lib/classes/redshift-repository.class.js +0 -129
  107. package/dist/esm/lib/classes/sql-writer.class.d.ts +0 -14
  108. package/dist/esm/lib/classes/table-migration-base.class.d.ts +0 -8
  109. package/dist/esm/lib/classes/table-migration-base.class.js +0 -19
  110. package/dist/esm/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
  111. package/dist/esm/lib/classes/type-orm-paging-repository.class.js +0 -16
  112. package/dist/esm/lib/classes/type-orm-repository.class.d.ts +0 -62
  113. package/dist/esm/lib/classes/type-orm-repository.class.js +0 -202
  114. package/dist/esm/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
  115. package/dist/esm/lib/constants/many-to-one-relation-options.constant.js +0 -4
  116. package/dist/esm/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
  117. package/dist/esm/lib/decorators/nullable-table-column.decorator.js +0 -12
  118. package/dist/esm/lib/decorators/primary-table-column.decorator.d.ts +0 -2
  119. package/dist/esm/lib/decorators/primary-table-column.decorator.js +0 -12
  120. package/dist/esm/lib/decorators/table-column.decorator.d.ts +0 -2
  121. package/dist/esm/lib/decorators/table-column.decorator.js +0 -12
  122. package/dist/esm/lib/decorators/table.decorator.d.ts +0 -3
  123. package/dist/esm/lib/decorators/table.decorator.js +0 -11
  124. package/dist/esm/lib/functions/data-source-config-factory.function.d.ts +0 -3
  125. package/dist/esm/lib/functions/data-source-config-factory.function.js +0 -25
  126. package/dist/esm/lib/functions/data-source-factory.function.d.ts +0 -3
  127. package/dist/esm/lib/functions/data-source-factory.function.js +0 -7
  128. package/dist/esm/lib/functions/generate-date-query.function.d.ts +0 -2
  129. package/dist/esm/lib/functions/generate-date-query.function.js +0 -16
  130. package/dist/esm/lib/functions/get-api-type-from-column.function.d.ts +0 -2
  131. package/dist/esm/lib/functions/get-api-type-from-column.function.js +0 -14
  132. package/dist/esm/lib/functions/get-paging-key.function.d.ts +0 -1
  133. package/dist/esm/lib/functions/get-paging-key.function.js +0 -6
  134. package/dist/esm/lib/functions/get-skip.function.d.ts +0 -1
  135. package/dist/esm/lib/functions/get-skip.function.js +0 -6
  136. package/dist/esm/lib/functions/remove-falsey-keys.function.d.ts +0 -1
  137. package/dist/esm/lib/functions/remove-falsey-keys.function.js +0 -11
  138. package/dist/esm/lib/server-typeorm-postgres.module.d.ts +0 -5
  139. package/dist/esm/lib/server-typeorm-postgres.module.js +0 -49
  140. package/dist/esm/lib/types/data-source-options.interface.d.ts +0 -11
  141. package/dist/esm/lib/types/data-source-options.interface.js +0 -2
  142. package/dist/esm/lib/types/entity-provider.interface.d.ts +0 -9
  143. package/dist/esm/lib/types/entity-provider.interface.js +0 -2
  144. package/dist/esm/lib/types/page-params.interface.js +0 -2
  145. package/dist/esm/lib/types/paged-data.interface.d.ts +0 -6
  146. package/dist/esm/lib/types/paged-data.interface.js +0 -2
  147. package/dist/esm/lib/types/table-meta.type.d.ts +0 -9
  148. package/dist/esm/lib/types/table-meta.type.js +0 -2
  149. package/dist/types/index.d.ts +0 -28
  150. package/dist/types/lib/classes/column-migration-base.class.d.ts +0 -8
  151. package/dist/types/lib/classes/columns-migration-base.class.d.ts +0 -8
  152. package/dist/types/lib/classes/drop-column-migration-base.class.d.ts +0 -8
  153. package/dist/types/lib/classes/drop-table-migration-base.class.d.ts +0 -8
  154. package/dist/types/lib/classes/index-migration-base.class.d.ts +0 -8
  155. package/dist/types/lib/classes/redshift-repository.class.d.ts +0 -29
  156. package/dist/types/lib/classes/sql-writer.class.d.ts +0 -14
  157. package/dist/types/lib/classes/table-migration-base.class.d.ts +0 -8
  158. package/dist/types/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
  159. package/dist/types/lib/classes/type-orm-repository.class.d.ts +0 -62
  160. package/dist/types/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
  161. package/dist/types/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
  162. package/dist/types/lib/decorators/primary-table-column.decorator.d.ts +0 -2
  163. package/dist/types/lib/decorators/table-column.decorator.d.ts +0 -2
  164. package/dist/types/lib/decorators/table.decorator.d.ts +0 -3
  165. package/dist/types/lib/functions/data-source-config-factory.function.d.ts +0 -3
  166. package/dist/types/lib/functions/data-source-factory.function.d.ts +0 -3
  167. package/dist/types/lib/functions/generate-date-query.function.d.ts +0 -2
  168. package/dist/types/lib/functions/get-api-type-from-column.function.d.ts +0 -2
  169. package/dist/types/lib/functions/get-paging-key.function.d.ts +0 -1
  170. package/dist/types/lib/functions/get-skip.function.d.ts +0 -1
  171. package/dist/types/lib/functions/remove-falsey-keys.function.d.ts +0 -1
  172. package/dist/types/lib/server-typeorm-postgres.module.d.ts +0 -5
  173. package/dist/types/lib/types/data-source-options.interface.d.ts +0 -11
  174. package/dist/types/lib/types/entity-provider.interface.d.ts +0 -9
  175. package/dist/types/lib/types/page-params.interface.d.ts +0 -4
  176. package/dist/types/lib/types/paged-data.interface.d.ts +0 -6
  177. package/dist/types/lib/types/table-meta.type.d.ts +0 -9
  178. /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,728 @@
1
+ # @onivoro/server-typeorm-postgres
2
+
3
+ A TypeORM PostgreSQL integration library providing repository patterns, SQL generation utilities, migration base classes, and PostgreSQL/Redshift-specific optimizations for enterprise-scale applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @onivoro/server-typeorm-postgres
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **TypeORM Repository Pattern**: Enhanced repository with PostgreSQL-specific features
14
+ - **Redshift Repository**: Specialized repository for Amazon Redshift operations
15
+ - **SQL Writer**: Static utility class for PostgreSQL DDL generation
16
+ - **Migration Base Classes**: Simplified migration classes for common operations
17
+ - **Custom Decorators**: Table and column decorators for entity definitions
18
+ - **Pagination Support**: Abstract paging repository for custom implementations
19
+ - **Type Safety**: Full TypeScript support with comprehensive type definitions
20
+
21
+ ## Quick Start
22
+
23
+ ### Module Import
24
+
25
+ ```typescript
26
+ import { ServerTypeormPostgresModule } from '@onivoro/server-typeorm-postgres';
27
+
28
+ @Module({
29
+ imports: [
30
+ ServerTypeormPostgresModule
31
+ ],
32
+ })
33
+ export class AppModule {}
34
+ ```
35
+
36
+ ### Entity Definition with Custom Decorators
37
+
38
+ ```typescript
39
+ import {
40
+ Table,
41
+ PrimaryTableColumn,
42
+ TableColumn,
43
+ NullableTableColumn
44
+ } from '@onivoro/server-typeorm-postgres';
45
+
46
+ @Table({ name: 'users' })
47
+ export class User {
48
+ @PrimaryTableColumn()
49
+ id: number;
50
+
51
+ @TableColumn({ type: 'varchar', length: 255, unique: true })
52
+ email: string;
53
+
54
+ @TableColumn({ type: 'varchar', length: 100 })
55
+ firstName: string;
56
+
57
+ @NullableTableColumn({ type: 'timestamp' })
58
+ lastLoginAt?: Date;
59
+
60
+ @TableColumn({ type: 'boolean', default: true })
61
+ isActive: boolean;
62
+
63
+ @TableColumn({ type: 'jsonb', default: '{}' })
64
+ metadata: Record<string, any>;
65
+
66
+ @TableColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
67
+ createdAt: Date;
68
+
69
+ @NullableTableColumn({ type: 'timestamp' })
70
+ deletedAt?: Date;
71
+ }
72
+ ```
73
+
74
+ ## Repository Classes
75
+
76
+ ### TypeOrmRepository
77
+
78
+ Enhanced repository with PostgreSQL-specific methods:
79
+
80
+ ```typescript
81
+ import { Injectable } from '@nestjs/common';
82
+ import { TypeOrmRepository } from '@onivoro/server-typeorm-postgres';
83
+ import { EntityManager } from 'typeorm';
84
+ import { User } from './user.entity';
85
+
86
+ @Injectable()
87
+ export class UserRepository extends TypeOrmRepository<User> {
88
+ constructor(entityManager: EntityManager) {
89
+ super(User, entityManager);
90
+ }
91
+
92
+ // Core methods available:
93
+ async findByEmail(email: string): Promise<User> {
94
+ return this.getOne({ where: { email } });
95
+ }
96
+
97
+ async findActiveUsers(): Promise<User[]> {
98
+ return this.getMany({
99
+ where: { isActive: true }
100
+ });
101
+ }
102
+
103
+ async findUsersWithCount(): Promise<[User[], number]> {
104
+ return this.getManyAndCount({
105
+ where: { isActive: true }
106
+ });
107
+ }
108
+
109
+ async createUser(userData: Partial<User>): Promise<User> {
110
+ return this.postOne(userData);
111
+ }
112
+
113
+ async createUsers(usersData: Partial<User>[]): Promise<User[]> {
114
+ return this.postMany(usersData);
115
+ }
116
+
117
+ async updateUser(id: number, updates: Partial<User>): Promise<void> {
118
+ // patch() uses TypeORM's update() method
119
+ await this.patch({ id }, updates);
120
+ }
121
+
122
+ async replaceUser(id: number, userData: Partial<User>): Promise<void> {
123
+ // put() uses TypeORM's save() method
124
+ await this.put({ id }, userData);
125
+ }
126
+
127
+ async deleteUser(id: number): Promise<void> {
128
+ await this.delete({ id });
129
+ }
130
+
131
+ async softDeleteUser(id: number): Promise<void> {
132
+ await this.softDelete({ id });
133
+ }
134
+
135
+ // Transaction support
136
+ async createUserInTransaction(userData: Partial<User>, entityManager: EntityManager): Promise<User> {
137
+ const txRepository = this.forTransaction(entityManager);
138
+ return txRepository.postOne(userData);
139
+ }
140
+
141
+ // Custom queries with mapping
142
+ async findUsersByMetadata(key: string, value: any): Promise<User[]> {
143
+ const query = `
144
+ SELECT * FROM ${this.getTableNameExpression()}
145
+ WHERE metadata->>'${key}' = $1
146
+ AND deleted_at IS NULL
147
+ `;
148
+ return this.queryAndMap(query, [value]);
149
+ }
150
+
151
+ // Using ILike for case-insensitive search
152
+ async searchUsers(searchTerm: string): Promise<User[]> {
153
+ const filters = this.buildWhereILike({
154
+ firstName: searchTerm,
155
+ lastName: searchTerm,
156
+ email: searchTerm
157
+ });
158
+
159
+ return this.getMany({ where: filters });
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### TypeOrmPagingRepository
165
+
166
+ Abstract base class for implementing pagination:
167
+
168
+ ```typescript
169
+ import { Injectable } from '@nestjs/common';
170
+ import { TypeOrmPagingRepository, IPageParams, IPagedData } from '@onivoro/server-typeorm-postgres';
171
+ import { EntityManager, FindManyOptions } from 'typeorm';
172
+ import { User } from './user.entity';
173
+
174
+ // Define your custom params interface
175
+ interface UserPageParams {
176
+ isActive?: boolean;
177
+ search?: string;
178
+ departmentId?: number;
179
+ }
180
+
181
+ @Injectable()
182
+ export class UserPagingRepository extends TypeOrmPagingRepository<User, UserPageParams> {
183
+ constructor(entityManager: EntityManager) {
184
+ super(User, entityManager);
185
+ }
186
+
187
+ // You must implement the abstract getPage method
188
+ async getPage(pageParams: IPageParams, params: UserPageParams): Promise<IPagedData<User>> {
189
+ const { page, limit } = pageParams;
190
+ const skip = this.getSkip(page, limit);
191
+
192
+ // Build where conditions
193
+ const where = this.removeFalseyKeys({
194
+ isActive: params.isActive,
195
+ departmentId: params.departmentId
196
+ });
197
+
198
+ // Add search conditions if provided
199
+ if (params.search) {
200
+ Object.assign(where, this.buildWhereILike({
201
+ firstName: params.search,
202
+ lastName: params.search,
203
+ email: params.search
204
+ }));
205
+ }
206
+
207
+ const [data, total] = await this.getManyAndCount({
208
+ where,
209
+ skip,
210
+ take: limit,
211
+ order: { createdAt: 'DESC' }
212
+ });
213
+
214
+ return {
215
+ data,
216
+ total,
217
+ page,
218
+ limit,
219
+ totalPages: Math.ceil(total / limit),
220
+ hasNext: page < Math.ceil(total / limit),
221
+ hasPrev: page > 1
222
+ };
223
+ }
224
+
225
+ // You can add additional helper methods
226
+ getCacheKey(pageParams: IPageParams, params: UserPageParams): string {
227
+ return this.getPagingKey(pageParams.page, pageParams.limit) + '_' + JSON.stringify(params);
228
+ }
229
+ }
230
+ ```
231
+
232
+ ### RedshiftRepository
233
+
234
+ Specialized repository for Amazon Redshift with custom SQL building:
235
+
236
+ ```typescript
237
+ import { Injectable } from '@nestjs/common';
238
+ import { RedshiftRepository } from '@onivoro/server-typeorm-postgres';
239
+ import { EntityManager } from 'typeorm';
240
+ import { AnalyticsEvent } from './analytics-event.entity';
241
+
242
+ @Injectable()
243
+ export class AnalyticsRepository extends RedshiftRepository<AnalyticsEvent> {
244
+ constructor(entityManager: EntityManager) {
245
+ super(AnalyticsEvent, entityManager);
246
+ }
247
+
248
+ // RedshiftRepository overrides several methods for Redshift compatibility
249
+ async createAnalyticsEvent(event: Partial<AnalyticsEvent>): Promise<AnalyticsEvent> {
250
+ // Uses custom SQL building and retrieval
251
+ return this.postOne(event);
252
+ }
253
+
254
+ async bulkInsertEvents(events: Partial<AnalyticsEvent>[]): Promise<AnalyticsEvent[]> {
255
+ // Uses optimized bulk insert
256
+ return this.postMany(events);
257
+ }
258
+
259
+ // Performance-optimized methods unique to RedshiftRepository
260
+ async insertWithoutReturn(event: Partial<AnalyticsEvent>): Promise<void> {
261
+ // Inserts without performing retrieval query
262
+ await this.postOneWithoutReturn(event);
263
+ }
264
+
265
+ async bulkInsertWithoutReturn(events: Partial<AnalyticsEvent>[]): Promise<void> {
266
+ // NOTE: Currently throws NotImplementedException
267
+ // await this.postManyWithoutReturn(events);
268
+ }
269
+
270
+ // Custom analytics queries
271
+ async getEventAnalytics(startDate: Date, endDate: Date) {
272
+ const query = `
273
+ SELECT
274
+ event_type,
275
+ COUNT(*) as event_count,
276
+ COUNT(DISTINCT user_id) as unique_users,
277
+ DATE_TRUNC('day', created_at) as event_date
278
+ FROM ${this.getTableNameExpression()}
279
+ WHERE created_at BETWEEN $1 AND $2
280
+ GROUP BY event_type, event_date
281
+ ORDER BY event_date DESC, event_count DESC
282
+ `;
283
+
284
+ return this.query(query, [startDate, endDate]);
285
+ }
286
+
287
+ // Redshift handles JSONB differently
288
+ async findEventsByJsonData(key: string, value: any): Promise<AnalyticsEvent[]> {
289
+ // JSON_PARSE is used automatically for jsonb columns in Redshift
290
+ const query = `
291
+ SELECT * FROM ${this.getTableNameExpression()}
292
+ WHERE JSON_EXTRACT_PATH_TEXT(event_data, '${key}') = $1
293
+ `;
294
+
295
+ return this.queryAndMap(query, [value]);
296
+ }
297
+ }
298
+ ```
299
+
300
+ ## SQL Writer
301
+
302
+ Static utility class for generating PostgreSQL DDL:
303
+
304
+ ```typescript
305
+ import { SqlWriter } from '@onivoro/server-typeorm-postgres';
306
+ import { TableColumnOptions } from 'typeorm';
307
+
308
+ // Add single column
309
+ const addColumnSql = SqlWriter.addColumn('users', {
310
+ name: 'phone_number',
311
+ type: 'varchar',
312
+ length: 20,
313
+ isNullable: true
314
+ });
315
+ // Returns: ALTER TABLE "users" ADD "phone_number" varchar(20)
316
+
317
+ // Create table with multiple columns
318
+ const createTableSql = SqlWriter.createTable('products', [
319
+ { name: 'id', type: 'serial', isPrimary: true },
320
+ { name: 'name', type: 'varchar', length: 255, isNullable: false },
321
+ { name: 'price', type: 'decimal', precision: 10, scale: 2 },
322
+ { name: 'metadata', type: 'jsonb', default: {} },
323
+ { name: 'created_at', type: 'timestamp', default: 'CURRENT_TIMESTAMP' }
324
+ ]);
325
+
326
+ // Drop table
327
+ const dropTableSql = SqlWriter.dropTable('products');
328
+ // Returns: DROP TABLE "products";
329
+
330
+ // Add multiple columns
331
+ const addColumnsSql = SqlWriter.addColumns('products', [
332
+ { name: 'category_id', type: 'int', isNullable: true },
333
+ { name: 'sku', type: 'varchar', length: 50, isUnique: true }
334
+ ]);
335
+
336
+ // Drop column
337
+ const dropColumnSql = SqlWriter.dropColumn('products', { name: 'old_column' });
338
+ // Returns: ALTER TABLE "products" DROP COLUMN old_column
339
+
340
+ // Create indexes
341
+ const createIndexSql = SqlWriter.createIndex('products', 'name', false);
342
+ // Returns: CREATE INDEX IF NOT EXISTS products_name ON "products"(name)
343
+
344
+ const createUniqueIndexSql = SqlWriter.createUniqueIndex('products', 'sku');
345
+ // Returns: CREATE UNIQUE INDEX IF NOT EXISTS products_sku ON "products"(sku)
346
+
347
+ // Drop index
348
+ const dropIndexSql = SqlWriter.dropIndex('products_name');
349
+ // Returns: DROP INDEX IF EXISTS products_name
350
+
351
+ // Handle special default values
352
+ const jsonbColumn: TableColumnOptions = {
353
+ name: 'settings',
354
+ type: 'jsonb',
355
+ default: { notifications: true, theme: 'light' }
356
+ };
357
+ const jsonbSql = SqlWriter.addColumn('users', jsonbColumn);
358
+ // Returns: ALTER TABLE "users" ADD "settings" jsonb DEFAULT '{"notifications":true,"theme":"light"}'::jsonb
359
+
360
+ // Boolean and numeric defaults
361
+ const booleanSql = SqlWriter.addColumn('users', {
362
+ name: 'is_verified',
363
+ type: 'boolean',
364
+ default: false
365
+ });
366
+ // Returns: ALTER TABLE "users" ADD "is_verified" boolean DEFAULT FALSE
367
+ ```
368
+
369
+ ## Migration Base Classes
370
+
371
+ ### TableMigrationBase
372
+
373
+ ```typescript
374
+ import { TableMigrationBase } from '@onivoro/server-typeorm-postgres';
375
+ import { MigrationInterface } from 'typeorm';
376
+
377
+ export class CreateUsersTable1234567890 extends TableMigrationBase implements MigrationInterface {
378
+ constructor() {
379
+ super('users', [
380
+ { name: 'id', type: 'serial', isPrimary: true },
381
+ { name: 'email', type: 'varchar', length: 255, isUnique: true, isNullable: false },
382
+ { name: 'first_name', type: 'varchar', length: 100, isNullable: false },
383
+ { name: 'metadata', type: 'jsonb', default: '{}' },
384
+ { name: 'is_active', type: 'boolean', default: true },
385
+ { name: 'created_at', type: 'timestamp', default: 'CURRENT_TIMESTAMP' },
386
+ { name: 'deleted_at', type: 'timestamp', isNullable: true }
387
+ ]);
388
+ }
389
+ }
390
+ ```
391
+
392
+ ### ColumnMigrationBase
393
+
394
+ ```typescript
395
+ import { ColumnMigrationBase } from '@onivoro/server-typeorm-postgres';
396
+
397
+ export class AddUserPhoneNumber1234567891 extends ColumnMigrationBase {
398
+ constructor() {
399
+ super('users', {
400
+ name: 'phone_number',
401
+ type: 'varchar',
402
+ length: 20,
403
+ isNullable: true
404
+ });
405
+ }
406
+ }
407
+ ```
408
+
409
+ ### ColumnsMigrationBase
410
+
411
+ ```typescript
412
+ import { ColumnsMigrationBase } from '@onivoro/server-typeorm-postgres';
413
+
414
+ export class AddUserContactInfo1234567892 extends ColumnsMigrationBase {
415
+ constructor() {
416
+ super('users', [
417
+ { name: 'phone_number', type: 'varchar', length: 20, isNullable: true },
418
+ { name: 'secondary_email', type: 'varchar', length: 255, isNullable: true },
419
+ { name: 'address', type: 'jsonb', isNullable: true }
420
+ ]);
421
+ }
422
+ }
423
+ ```
424
+
425
+ ### IndexMigrationBase
426
+
427
+ ```typescript
428
+ import { IndexMigrationBase } from '@onivoro/server-typeorm-postgres';
429
+
430
+ export class CreateUserEmailIndex1234567893 extends IndexMigrationBase {
431
+ constructor() {
432
+ super('users', 'email', true); // table, column, unique
433
+ }
434
+ }
435
+ ```
436
+
437
+ ### DropTableMigrationBase & DropColumnMigrationBase
438
+
439
+ ```typescript
440
+ import { DropTableMigrationBase, DropColumnMigrationBase } from '@onivoro/server-typeorm-postgres';
441
+
442
+ export class DropLegacyUsersTable1234567894 extends DropTableMigrationBase {
443
+ constructor() {
444
+ super('legacy_users');
445
+ }
446
+ }
447
+
448
+ export class DropUserMiddleName1234567895 extends DropColumnMigrationBase {
449
+ constructor() {
450
+ super('users', { name: 'middle_name' });
451
+ }
452
+ }
453
+ ```
454
+
455
+ ## Building Repositories from Metadata
456
+
457
+ Both TypeOrmRepository and RedshiftRepository support building instances from metadata:
458
+
459
+ ```typescript
460
+ import { TypeOrmRepository, RedshiftRepository } from '@onivoro/server-typeorm-postgres';
461
+ import { DataSource } from 'typeorm';
462
+
463
+ // Define your entity type
464
+ interface UserEvent {
465
+ id: number;
466
+ userId: number;
467
+ eventType: string;
468
+ eventData: any;
469
+ createdAt: Date;
470
+ }
471
+
472
+ // Build TypeORM repository from metadata
473
+ const userEventRepo = TypeOrmRepository.buildFromMetadata<UserEvent>(dataSource, {
474
+ schema: 'public',
475
+ table: 'user_events',
476
+ columns: {
477
+ id: {
478
+ databasePath: 'id',
479
+ type: 'int',
480
+ propertyPath: 'id',
481
+ isPrimary: true,
482
+ default: undefined
483
+ },
484
+ userId: {
485
+ databasePath: 'user_id',
486
+ type: 'int',
487
+ propertyPath: 'userId',
488
+ isPrimary: false,
489
+ default: undefined
490
+ },
491
+ eventType: {
492
+ databasePath: 'event_type',
493
+ type: 'varchar',
494
+ propertyPath: 'eventType',
495
+ isPrimary: false,
496
+ default: undefined
497
+ },
498
+ eventData: {
499
+ databasePath: 'event_data',
500
+ type: 'jsonb',
501
+ propertyPath: 'eventData',
502
+ isPrimary: false,
503
+ default: {}
504
+ },
505
+ createdAt: {
506
+ databasePath: 'created_at',
507
+ type: 'timestamp',
508
+ propertyPath: 'createdAt',
509
+ isPrimary: false,
510
+ default: 'CURRENT_TIMESTAMP'
511
+ }
512
+ }
513
+ });
514
+
515
+ // Build Redshift repository from metadata
516
+ const analyticsRepo = RedshiftRepository.buildFromMetadata<UserEvent>(redshiftDataSource, {
517
+ schema: 'analytics',
518
+ table: 'user_events',
519
+ columns: {
520
+ // Same column definitions as above
521
+ }
522
+ });
523
+
524
+ // Use the repositories
525
+ const events = await userEventRepo.getMany({ where: { userId: 123 } });
526
+ const recentEvent = await userEventRepo.getOne({ where: { id: 456 } });
527
+ ```
528
+
529
+ ## Data Source Configuration
530
+
531
+ ```typescript
532
+ import { dataSourceFactory, dataSourceConfigFactory } from '@onivoro/server-typeorm-postgres';
533
+ import { User, Product, Order } from './entities';
534
+
535
+ // Using data source factory
536
+ const dataSource = dataSourceFactory('postgres-main', {
537
+ host: 'localhost',
538
+ port: 5432,
539
+ username: 'postgres',
540
+ password: 'password',
541
+ database: 'myapp'
542
+ }, [User, Product, Order]);
543
+
544
+ // Using config factory for more control
545
+ const config = dataSourceConfigFactory('postgres-main', {
546
+ host: process.env.DB_HOST,
547
+ port: parseInt(process.env.DB_PORT),
548
+ username: process.env.DB_USERNAME,
549
+ password: process.env.DB_PASSWORD,
550
+ database: process.env.DB_DATABASE,
551
+ ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
552
+ }, [User, Product, Order]);
553
+
554
+ const dataSource = new DataSource(config);
555
+ ```
556
+
557
+ ## Type Definitions
558
+
559
+ ### Core Types
560
+
561
+ ```typescript
562
+ // Table metadata
563
+ interface TTableMeta {
564
+ databasePath: string;
565
+ type: string;
566
+ propertyPath: string;
567
+ isPrimary: boolean;
568
+ default?: any;
569
+ }
570
+
571
+ // Page parameters
572
+ interface IPageParams {
573
+ page: number;
574
+ limit: number;
575
+ }
576
+
577
+ // Paged data result
578
+ interface IPagedData<T> {
579
+ data: T[];
580
+ total: number;
581
+ page: number;
582
+ limit: number;
583
+ totalPages: number;
584
+ hasNext: boolean;
585
+ hasPrev: boolean;
586
+ }
587
+
588
+ // Data source options
589
+ interface IDataSourceOptions {
590
+ host: string;
591
+ port: number;
592
+ username: string;
593
+ password: string;
594
+ database: string;
595
+ ssl?: any;
596
+ extra?: any;
597
+ }
598
+
599
+ // Entity provider interface
600
+ interface IEntityProvider<TEntity, TFindOneOptions, TFindManyOptions, TFindOptionsWhere, TUpdateData> {
601
+ getOne(options: TFindOneOptions): Promise<TEntity>;
602
+ getMany(options: TFindManyOptions): Promise<TEntity[]>;
603
+ getManyAndCount(options: TFindManyOptions): Promise<[TEntity[], number]>;
604
+ postOne(body: Partial<TEntity>): Promise<TEntity>;
605
+ postMany(body: Partial<TEntity>[]): Promise<TEntity[]>;
606
+ delete(options: TFindOptionsWhere): Promise<void>;
607
+ softDelete(options: TFindOptionsWhere): Promise<void>;
608
+ put(options: TFindOptionsWhere, body: TUpdateData): Promise<void>;
609
+ patch(options: TFindOptionsWhere, body: TUpdateData): Promise<void>;
610
+ }
611
+ ```
612
+
613
+ ## Utility Functions
614
+
615
+ ```typescript
616
+ import {
617
+ getSkip,
618
+ getPagingKey,
619
+ removeFalseyKeys,
620
+ generateDateQuery,
621
+ getApiTypeFromColumn
622
+ } from '@onivoro/server-typeorm-postgres';
623
+
624
+ // Calculate skip value for pagination
625
+ const skip = getSkip(2, 20); // page 2, limit 20 = skip 20
626
+
627
+ // Generate cache key for pagination
628
+ const cacheKey = getPagingKey(2, 20); // Returns: "page_2_limit_20"
629
+
630
+ // Remove falsey values from object
631
+ const cleanedFilters = removeFalseyKeys({
632
+ name: 'John',
633
+ age: 0, // Removed
634
+ active: false, // Kept (false is not falsey for this function)
635
+ email: '', // Removed
636
+ dept: null // Removed
637
+ });
638
+
639
+ // Generate date range query
640
+ const dateQuery = generateDateQuery('created_at', {
641
+ startDate: new Date('2024-01-01'),
642
+ endDate: new Date('2024-12-31')
643
+ });
644
+
645
+ // Get API type from TypeORM column metadata
646
+ const apiType = getApiTypeFromColumn(columnMetadata);
647
+ ```
648
+
649
+ ## Best Practices
650
+
651
+ 1. **Repository Pattern**: Extend TypeOrmRepository for standard PostgreSQL operations
652
+ 2. **Redshift Operations**: Use RedshiftRepository for analytics workloads with specific optimizations
653
+ 3. **Pagination**: Implement TypeOrmPagingRepository for consistent pagination across your app
654
+ 4. **Migrations**: Use migration base classes for consistent schema management
655
+ 5. **SQL Generation**: Use SqlWriter for complex DDL operations
656
+ 6. **Transactions**: Use `forTransaction()` to create transaction-scoped repositories
657
+ 7. **Performance**: For Redshift bulk inserts, use `postOneWithoutReturn()` when you don't need the inserted record back
658
+ 8. **Type Safety**: Leverage the strongly-typed column metadata for compile-time safety
659
+
660
+ ## Testing
661
+
662
+ ```typescript
663
+ import { Test } from '@nestjs/testing';
664
+ import { TypeOrmModule } from '@nestjs/typeorm';
665
+ import { EntityManager } from 'typeorm';
666
+ import { UserRepository } from './user.repository';
667
+ import { User } from './user.entity';
668
+
669
+ describe('UserRepository', () => {
670
+ let repository: UserRepository;
671
+ let entityManager: EntityManager;
672
+
673
+ beforeEach(async () => {
674
+ const module = await Test.createTestingModule({
675
+ imports: [
676
+ TypeOrmModule.forRoot({
677
+ type: 'postgres',
678
+ host: 'localhost',
679
+ port: 5432,
680
+ username: 'test',
681
+ password: 'test',
682
+ database: 'test_db',
683
+ entities: [User],
684
+ synchronize: true,
685
+ }),
686
+ TypeOrmModule.forFeature([User])
687
+ ],
688
+ providers: [UserRepository],
689
+ }).compile();
690
+
691
+ entityManager = module.get<EntityManager>(EntityManager);
692
+ repository = new UserRepository(entityManager);
693
+ });
694
+
695
+ it('should create and retrieve user', async () => {
696
+ const userData = {
697
+ email: 'test@example.com',
698
+ firstName: 'John',
699
+ isActive: true
700
+ };
701
+
702
+ const user = await repository.postOne(userData);
703
+ expect(user.id).toBeDefined();
704
+ expect(user.email).toBe('test@example.com');
705
+
706
+ const foundUser = await repository.getOne({ where: { id: user.id } });
707
+ expect(foundUser).toEqual(user);
708
+ });
709
+
710
+ it('should handle transactions', async () => {
711
+ await entityManager.transaction(async (transactionalEntityManager) => {
712
+ const txRepository = repository.forTransaction(transactionalEntityManager);
713
+
714
+ await txRepository.postOne({
715
+ email: 'tx@example.com',
716
+ firstName: 'Transaction',
717
+ isActive: true
718
+ });
719
+
720
+ // Transaction will be rolled back after test
721
+ });
722
+ });
723
+ });
724
+ ```
725
+
726
+ ## License
727
+
728
+ This library is part of the Onivoro monorepo ecosystem.