@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.
- package/README.md +728 -0
- package/{dist/cjs/index.d.ts → index.ts} +6 -1
- package/jest.config.ts +11 -0
- package/package.json +8 -47
- package/project.json +23 -0
- package/{dist/esm/index.d.ts → src/index.ts} +6 -1
- package/src/lib/classes/__snapshots__/sql-writer.class.spec.ts.snap +8 -0
- package/src/lib/classes/column-migration-base.class.ts +17 -0
- package/src/lib/classes/columns-migration-base.class.ts +17 -0
- package/src/lib/classes/drop-column-migration-base.class.ts +17 -0
- package/src/lib/classes/drop-table-migration-base.class.ts +17 -0
- package/src/lib/classes/index-migration-base.class.ts +17 -0
- package/src/lib/classes/redshift-repository.class.ts +146 -0
- package/src/lib/classes/sql-writer.class.spec.ts +20 -0
- package/{dist/esm/lib/classes/sql-writer.class.js → src/lib/classes/sql-writer.class.ts} +29 -16
- package/src/lib/classes/table-migration-base.class.ts +17 -0
- package/src/lib/classes/type-orm-paging-repository.class.ts +22 -0
- package/src/lib/classes/type-orm-repository.class.ts +299 -0
- package/src/lib/constants/many-to-one-relation-options.constant.ts +3 -0
- package/src/lib/decorators/nullable-table-column.decorator.ts +9 -0
- package/src/lib/decorators/primary-table-column.decorator.ts +9 -0
- package/src/lib/decorators/table-column.decorator.ts +9 -0
- package/src/lib/decorators/table.decorator.ts +8 -0
- package/src/lib/functions/data-source-config-factory.function.ts +34 -0
- package/src/lib/functions/data-source-factory.function.ts +10 -0
- package/src/lib/functions/generate-date-query.function.ts +20 -0
- package/src/lib/functions/get-api-type-from-column.function.ts +14 -0
- package/src/lib/functions/get-paging-key.function.ts +3 -0
- package/src/lib/functions/get-skip.function.ts +3 -0
- package/src/lib/functions/remove-falsey-keys.function.ts +8 -0
- package/src/lib/server-typeorm-postgres.module.ts +44 -0
- package/src/lib/types/data-source-options.interface.ts +11 -0
- package/{dist/cjs/lib/types/entity-provider.interface.d.ts → src/lib/types/entity-provider.interface.ts} +2 -2
- package/{dist/esm/lib/types/page-params.interface.d.ts → src/lib/types/page-params.interface.ts} +2 -0
- package/src/lib/types/table-meta.type.ts +4 -0
- package/tsconfig.json +17 -0
- package/tsconfig.lib.json +8 -0
- package/tsconfig.spec.json +21 -0
- package/dist/cjs/index.js +0 -44
- package/dist/cjs/lib/classes/column-migration-base.class.d.ts +0 -8
- package/dist/cjs/lib/classes/column-migration-base.class.js +0 -19
- package/dist/cjs/lib/classes/columns-migration-base.class.d.ts +0 -8
- package/dist/cjs/lib/classes/columns-migration-base.class.js +0 -19
- package/dist/cjs/lib/classes/drop-column-migration-base.class.d.ts +0 -8
- package/dist/cjs/lib/classes/drop-column-migration-base.class.js +0 -19
- package/dist/cjs/lib/classes/drop-table-migration-base.class.d.ts +0 -8
- package/dist/cjs/lib/classes/drop-table-migration-base.class.js +0 -19
- package/dist/cjs/lib/classes/index-migration-base.class.d.ts +0 -8
- package/dist/cjs/lib/classes/index-migration-base.class.js +0 -19
- package/dist/cjs/lib/classes/redshift-repository.class.d.ts +0 -29
- package/dist/cjs/lib/classes/redshift-repository.class.js +0 -129
- package/dist/cjs/lib/classes/sql-writer.class.d.ts +0 -14
- package/dist/cjs/lib/classes/sql-writer.class.js +0 -53
- package/dist/cjs/lib/classes/table-migration-base.class.d.ts +0 -8
- package/dist/cjs/lib/classes/table-migration-base.class.js +0 -19
- package/dist/cjs/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
- package/dist/cjs/lib/classes/type-orm-paging-repository.class.js +0 -16
- package/dist/cjs/lib/classes/type-orm-repository.class.d.ts +0 -62
- package/dist/cjs/lib/classes/type-orm-repository.class.js +0 -202
- package/dist/cjs/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
- package/dist/cjs/lib/constants/many-to-one-relation-options.constant.js +0 -4
- package/dist/cjs/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
- package/dist/cjs/lib/decorators/nullable-table-column.decorator.js +0 -12
- package/dist/cjs/lib/decorators/primary-table-column.decorator.d.ts +0 -2
- package/dist/cjs/lib/decorators/primary-table-column.decorator.js +0 -12
- package/dist/cjs/lib/decorators/table-column.decorator.d.ts +0 -2
- package/dist/cjs/lib/decorators/table-column.decorator.js +0 -12
- package/dist/cjs/lib/decorators/table.decorator.d.ts +0 -3
- package/dist/cjs/lib/decorators/table.decorator.js +0 -11
- package/dist/cjs/lib/functions/data-source-config-factory.function.d.ts +0 -3
- package/dist/cjs/lib/functions/data-source-config-factory.function.js +0 -25
- package/dist/cjs/lib/functions/data-source-factory.function.d.ts +0 -3
- package/dist/cjs/lib/functions/data-source-factory.function.js +0 -7
- package/dist/cjs/lib/functions/generate-date-query.function.d.ts +0 -2
- package/dist/cjs/lib/functions/generate-date-query.function.js +0 -16
- package/dist/cjs/lib/functions/get-api-type-from-column.function.d.ts +0 -2
- package/dist/cjs/lib/functions/get-api-type-from-column.function.js +0 -14
- package/dist/cjs/lib/functions/get-paging-key.function.d.ts +0 -1
- package/dist/cjs/lib/functions/get-paging-key.function.js +0 -6
- package/dist/cjs/lib/functions/get-skip.function.d.ts +0 -1
- package/dist/cjs/lib/functions/get-skip.function.js +0 -6
- package/dist/cjs/lib/functions/remove-falsey-keys.function.d.ts +0 -1
- package/dist/cjs/lib/functions/remove-falsey-keys.function.js +0 -11
- package/dist/cjs/lib/server-typeorm-postgres.module.d.ts +0 -5
- package/dist/cjs/lib/server-typeorm-postgres.module.js +0 -49
- package/dist/cjs/lib/types/data-source-options.interface.d.ts +0 -11
- package/dist/cjs/lib/types/data-source-options.interface.js +0 -2
- package/dist/cjs/lib/types/entity-provider.interface.js +0 -2
- package/dist/cjs/lib/types/page-params.interface.d.ts +0 -4
- package/dist/cjs/lib/types/page-params.interface.js +0 -2
- package/dist/cjs/lib/types/paged-data.interface.js +0 -2
- package/dist/cjs/lib/types/table-meta.type.d.ts +0 -9
- package/dist/cjs/lib/types/table-meta.type.js +0 -2
- package/dist/esm/index.js +0 -44
- package/dist/esm/lib/classes/column-migration-base.class.d.ts +0 -8
- package/dist/esm/lib/classes/column-migration-base.class.js +0 -19
- package/dist/esm/lib/classes/columns-migration-base.class.d.ts +0 -8
- package/dist/esm/lib/classes/columns-migration-base.class.js +0 -19
- package/dist/esm/lib/classes/drop-column-migration-base.class.d.ts +0 -8
- package/dist/esm/lib/classes/drop-column-migration-base.class.js +0 -19
- package/dist/esm/lib/classes/drop-table-migration-base.class.d.ts +0 -8
- package/dist/esm/lib/classes/drop-table-migration-base.class.js +0 -19
- package/dist/esm/lib/classes/index-migration-base.class.d.ts +0 -8
- package/dist/esm/lib/classes/index-migration-base.class.js +0 -19
- package/dist/esm/lib/classes/redshift-repository.class.d.ts +0 -29
- package/dist/esm/lib/classes/redshift-repository.class.js +0 -129
- package/dist/esm/lib/classes/sql-writer.class.d.ts +0 -14
- package/dist/esm/lib/classes/table-migration-base.class.d.ts +0 -8
- package/dist/esm/lib/classes/table-migration-base.class.js +0 -19
- package/dist/esm/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
- package/dist/esm/lib/classes/type-orm-paging-repository.class.js +0 -16
- package/dist/esm/lib/classes/type-orm-repository.class.d.ts +0 -62
- package/dist/esm/lib/classes/type-orm-repository.class.js +0 -202
- package/dist/esm/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
- package/dist/esm/lib/constants/many-to-one-relation-options.constant.js +0 -4
- package/dist/esm/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
- package/dist/esm/lib/decorators/nullable-table-column.decorator.js +0 -12
- package/dist/esm/lib/decorators/primary-table-column.decorator.d.ts +0 -2
- package/dist/esm/lib/decorators/primary-table-column.decorator.js +0 -12
- package/dist/esm/lib/decorators/table-column.decorator.d.ts +0 -2
- package/dist/esm/lib/decorators/table-column.decorator.js +0 -12
- package/dist/esm/lib/decorators/table.decorator.d.ts +0 -3
- package/dist/esm/lib/decorators/table.decorator.js +0 -11
- package/dist/esm/lib/functions/data-source-config-factory.function.d.ts +0 -3
- package/dist/esm/lib/functions/data-source-config-factory.function.js +0 -25
- package/dist/esm/lib/functions/data-source-factory.function.d.ts +0 -3
- package/dist/esm/lib/functions/data-source-factory.function.js +0 -7
- package/dist/esm/lib/functions/generate-date-query.function.d.ts +0 -2
- package/dist/esm/lib/functions/generate-date-query.function.js +0 -16
- package/dist/esm/lib/functions/get-api-type-from-column.function.d.ts +0 -2
- package/dist/esm/lib/functions/get-api-type-from-column.function.js +0 -14
- package/dist/esm/lib/functions/get-paging-key.function.d.ts +0 -1
- package/dist/esm/lib/functions/get-paging-key.function.js +0 -6
- package/dist/esm/lib/functions/get-skip.function.d.ts +0 -1
- package/dist/esm/lib/functions/get-skip.function.js +0 -6
- package/dist/esm/lib/functions/remove-falsey-keys.function.d.ts +0 -1
- package/dist/esm/lib/functions/remove-falsey-keys.function.js +0 -11
- package/dist/esm/lib/server-typeorm-postgres.module.d.ts +0 -5
- package/dist/esm/lib/server-typeorm-postgres.module.js +0 -49
- package/dist/esm/lib/types/data-source-options.interface.d.ts +0 -11
- package/dist/esm/lib/types/data-source-options.interface.js +0 -2
- package/dist/esm/lib/types/entity-provider.interface.d.ts +0 -9
- package/dist/esm/lib/types/entity-provider.interface.js +0 -2
- package/dist/esm/lib/types/page-params.interface.js +0 -2
- package/dist/esm/lib/types/paged-data.interface.d.ts +0 -6
- package/dist/esm/lib/types/paged-data.interface.js +0 -2
- package/dist/esm/lib/types/table-meta.type.d.ts +0 -9
- package/dist/esm/lib/types/table-meta.type.js +0 -2
- package/dist/types/index.d.ts +0 -28
- package/dist/types/lib/classes/column-migration-base.class.d.ts +0 -8
- package/dist/types/lib/classes/columns-migration-base.class.d.ts +0 -8
- package/dist/types/lib/classes/drop-column-migration-base.class.d.ts +0 -8
- package/dist/types/lib/classes/drop-table-migration-base.class.d.ts +0 -8
- package/dist/types/lib/classes/index-migration-base.class.d.ts +0 -8
- package/dist/types/lib/classes/redshift-repository.class.d.ts +0 -29
- package/dist/types/lib/classes/sql-writer.class.d.ts +0 -14
- package/dist/types/lib/classes/table-migration-base.class.d.ts +0 -8
- package/dist/types/lib/classes/type-orm-paging-repository.class.d.ts +0 -14
- package/dist/types/lib/classes/type-orm-repository.class.d.ts +0 -62
- package/dist/types/lib/constants/many-to-one-relation-options.constant.d.ts +0 -2
- package/dist/types/lib/decorators/nullable-table-column.decorator.d.ts +0 -2
- package/dist/types/lib/decorators/primary-table-column.decorator.d.ts +0 -2
- package/dist/types/lib/decorators/table-column.decorator.d.ts +0 -2
- package/dist/types/lib/decorators/table.decorator.d.ts +0 -3
- package/dist/types/lib/functions/data-source-config-factory.function.d.ts +0 -3
- package/dist/types/lib/functions/data-source-factory.function.d.ts +0 -3
- package/dist/types/lib/functions/generate-date-query.function.d.ts +0 -2
- package/dist/types/lib/functions/get-api-type-from-column.function.d.ts +0 -2
- package/dist/types/lib/functions/get-paging-key.function.d.ts +0 -1
- package/dist/types/lib/functions/get-skip.function.d.ts +0 -1
- package/dist/types/lib/functions/remove-falsey-keys.function.d.ts +0 -1
- package/dist/types/lib/server-typeorm-postgres.module.d.ts +0 -5
- package/dist/types/lib/types/data-source-options.interface.d.ts +0 -11
- package/dist/types/lib/types/entity-provider.interface.d.ts +0 -9
- package/dist/types/lib/types/page-params.interface.d.ts +0 -4
- package/dist/types/lib/types/paged-data.interface.d.ts +0 -6
- package/dist/types/lib/types/table-meta.type.d.ts +0 -9
- /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.
|