@woltz/rich-domain 0.2.1 → 0.2.2
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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mapper.d.ts +4 -0
- package/dist/mapper.d.ts.map +1 -0
- package/dist/mapper.js +3 -0
- package/dist/mapper.js.map +1 -0
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +0 -3
- package/dist/paginated-result.js.map +1 -1
- package/dist/repository/base-repository.d.ts +25 -48
- package/dist/repository/base-repository.d.ts.map +1 -1
- package/dist/repository/base-repository.js +7 -51
- package/dist/repository/base-repository.js.map +1 -1
- package/dist/repository/in-memory-repository.d.ts +12 -8
- package/dist/repository/in-memory-repository.d.ts.map +1 -1
- package/dist/repository/in-memory-repository.js +18 -10
- package/dist/repository/in-memory-repository.js.map +1 -1
- package/dist/repository/index.d.ts +2 -3
- package/dist/repository/index.d.ts.map +1 -1
- package/dist/repository/index.js +2 -2
- package/dist/repository/index.js.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/unit-of-work.d.ts +2 -2
- package/dist/types/unit-of-work.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/mapper.ts +3 -0
- package/src/paginated-result.ts +1 -8
- package/src/repository/base-repository.ts +27 -115
- package/src/repository/in-memory-repository.ts +28 -16
- package/src/repository/index.ts +2 -3
- package/src/types/index.ts +0 -1
- package/src/types/unit-of-work.ts +3 -3
- package/tests/repository.test.ts +92 -78
- package/src/repository/mapper.ts +0 -74
- package/src/types/repository.ts +0 -51
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
// Base Repository - Abstract implementation with common logic
|
|
3
3
|
// ============================================================================
|
|
4
4
|
|
|
5
|
-
import type { Id } from "../id";
|
|
6
5
|
import type { Aggregate } from "../entity";
|
|
7
6
|
import type { Criteria } from "../criteria";
|
|
8
7
|
import { PaginatedResult } from "../paginated-result";
|
|
9
|
-
import
|
|
10
|
-
import type { IMapper } from "./mapper";
|
|
8
|
+
import { Mapper } from "../mapper";
|
|
11
9
|
|
|
12
10
|
/**
|
|
13
11
|
* Abstract base repository
|
|
@@ -33,120 +31,34 @@ import type { IMapper } from "./mapper";
|
|
|
33
31
|
* }
|
|
34
32
|
* ```
|
|
35
33
|
*/
|
|
36
|
-
export abstract class BaseRepository<
|
|
37
|
-
TDomain extends Aggregate<any>,
|
|
38
|
-
TPersistence = any
|
|
39
|
-
> implements IRepository<TDomain>
|
|
40
|
-
{
|
|
41
|
-
constructor(protected readonly mapper: IMapper<TDomain, TPersistence>) {}
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/
|
|
50
|
-
protected abstract insertOne(data: TPersistence): Promise<TPersistence>;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Update existing record in database
|
|
54
|
-
*/
|
|
55
|
-
protected abstract updateOne(
|
|
56
|
-
id: string,
|
|
57
|
-
data: TPersistence
|
|
58
|
-
): Promise<TPersistence>;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Delete record from database
|
|
62
|
-
*/
|
|
63
|
-
protected abstract deleteOne(id: string): Promise<void>;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Find record by ID in database
|
|
67
|
-
*/
|
|
68
|
-
protected abstract findOneById(id: string): Promise<TDomain | null>;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Find all records in database (no filtering)
|
|
72
|
-
*/
|
|
73
|
-
protected abstract findMany(): Promise<TDomain[]>;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Apply criteria to query (filtering, ordering, pagination)
|
|
77
|
-
* Returns [data, total]
|
|
78
|
-
*/
|
|
79
|
-
protected abstract applyCriteria(
|
|
80
|
-
criteria: Criteria<TDomain>
|
|
81
|
-
): Promise<PaginatedResult<TDomain>>;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Count records matching criteria
|
|
85
|
-
*/
|
|
86
|
-
protected abstract countByCriteria(
|
|
87
|
-
criteria?: Criteria<TDomain>
|
|
88
|
-
): Promise<number>;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Check if record exists by ID
|
|
92
|
-
*/
|
|
93
|
-
protected abstract existsById(id: string): Promise<boolean>;
|
|
94
|
-
|
|
95
|
-
// ============================================================================
|
|
96
|
-
// Public API - Implemented using abstract methods
|
|
97
|
-
// ============================================================================
|
|
98
|
-
|
|
99
|
-
async findById(id: Id): Promise<TDomain | null> {
|
|
100
|
-
const domain = await this.findOneById(id.value);
|
|
101
|
-
if (!domain) return null;
|
|
102
|
-
return domain;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async find(criteria: Criteria<TDomain>): Promise<PaginatedResult<TDomain>> {
|
|
106
|
-
return await this.applyCriteria(criteria);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async findAll(criteria?: Criteria<TDomain>): Promise<TDomain[]> {
|
|
110
|
-
if (criteria) {
|
|
111
|
-
const result = await this.find(criteria);
|
|
112
|
-
return result.data;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const domains = await this.findMany();
|
|
116
|
-
return domains;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async findOne(criteria: Criteria<TDomain>): Promise<TDomain | null> {
|
|
120
|
-
// Limit to 1 result
|
|
121
|
-
const limitedCriteria = criteria.clone().limit(1);
|
|
122
|
-
const result = await this.find(limitedCriteria);
|
|
123
|
-
|
|
124
|
-
return result.data.length > 0 ? result.data[0] : null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async save(aggregate: TDomain): Promise<void> {
|
|
128
|
-
const persistence = this.mapper.toPersistence(aggregate);
|
|
129
|
-
|
|
130
|
-
if (aggregate.isNew) {
|
|
131
|
-
await this.insertOne(persistence);
|
|
132
|
-
} else {
|
|
133
|
-
await this.updateOne(aggregate.id.value, persistence);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async delete(aggregate: TDomain): Promise<void> {
|
|
138
|
-
await this.deleteOne(aggregate.id.value);
|
|
139
|
-
}
|
|
35
|
+
export abstract class ReadRepository<Agg extends Aggregate<any>> {
|
|
36
|
+
abstract find(criteria: Criteria<Agg>): Promise<PaginatedResult<Agg>>;
|
|
37
|
+
abstract findById(id: string): Promise<Agg | null>;
|
|
38
|
+
abstract count(criteria: Criteria<Agg>): Promise<number>;
|
|
39
|
+
abstract exists(id: string): Promise<boolean>;
|
|
40
|
+
}
|
|
140
41
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
42
|
+
export abstract class WriteRepository<Agg extends Aggregate<any>> {
|
|
43
|
+
abstract create(entity: Agg): Promise<void>;
|
|
44
|
+
abstract update(entity: Agg): Promise<void>;
|
|
45
|
+
abstract delete(entity: Agg): Promise<void>;
|
|
46
|
+
}
|
|
144
47
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
48
|
+
export abstract class WriteAndRead<Agg extends Aggregate<any>> {
|
|
49
|
+
abstract find(criteria: Criteria<Agg>): Promise<PaginatedResult<Agg>>;
|
|
50
|
+
abstract findById(id: string): Promise<Agg | null>;
|
|
51
|
+
abstract create(entity: Agg): Promise<void>;
|
|
52
|
+
abstract update(entity: Agg): Promise<void>;
|
|
53
|
+
abstract delete(entity: Agg): Promise<void>;
|
|
54
|
+
abstract count(criteria: Criteria<Agg>): Promise<number>;
|
|
55
|
+
abstract exists(id: string): Promise<boolean>;
|
|
56
|
+
}
|
|
148
57
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
58
|
+
export abstract class Repository<
|
|
59
|
+
TDomain extends Aggregate<any>
|
|
60
|
+
> extends WriteAndRead<TDomain> {
|
|
61
|
+
protected abstract readonly mapperToDomain: Mapper<unknown, TDomain>;
|
|
62
|
+
protected abstract readonly mapperToPersistence: Mapper<TDomain, unknown>;
|
|
63
|
+
abstract get model(): any;
|
|
152
64
|
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// In-Memory Repository - Perfect for testing
|
|
3
3
|
// ============================================================================
|
|
4
4
|
|
|
5
|
-
import type { Id } from "../id";
|
|
6
5
|
import type { Aggregate } from "../entity";
|
|
7
6
|
import type { Criteria } from "../criteria";
|
|
8
7
|
import { PaginatedResult } from "../paginated-result";
|
|
9
|
-
import
|
|
8
|
+
import { Repository } from "./base-repository";
|
|
9
|
+
import { Mapper } from "../mapper";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* In-memory repository implementation
|
|
@@ -23,13 +23,25 @@ import type { IRepository } from "../types";
|
|
|
23
23
|
* );
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
|
-
export class InMemoryRepository<
|
|
27
|
-
|
|
28
|
-
{
|
|
26
|
+
export class InMemoryRepository<
|
|
27
|
+
TDomain extends Aggregate<any>
|
|
28
|
+
> extends Repository<TDomain> {
|
|
29
29
|
protected items: Map<string, TDomain> = new Map();
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
constructor(
|
|
32
|
+
protected readonly mapperToDomain: Mapper<unknown, TDomain>,
|
|
33
|
+
protected readonly mapperToPersistence: Mapper<TDomain, unknown>
|
|
34
|
+
) {
|
|
35
|
+
super();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get model(): any {
|
|
39
|
+
// your database table name
|
|
40
|
+
return "inMemory";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async findById(id: string): Promise<TDomain | null> {
|
|
44
|
+
return this.items.get(id) || null;
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
async find(criteria: Criteria<TDomain>): Promise<PaginatedResult<TDomain>> {
|
|
@@ -51,13 +63,17 @@ export class InMemoryRepository<TDomain extends Aggregate<any>>
|
|
|
51
63
|
return result.data.length > 0 ? result.data[0] : null;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
async
|
|
66
|
+
async create(aggregate: TDomain): Promise<void> {
|
|
55
67
|
this.items.set(aggregate.id.value, aggregate);
|
|
56
68
|
}
|
|
57
69
|
|
|
58
|
-
async
|
|
70
|
+
async update(entity: TDomain): Promise<void> {
|
|
71
|
+
this.items.set(entity.id.value, entity);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async createMany(aggregates: TDomain[]): Promise<void> {
|
|
59
75
|
for (const aggregate of aggregates) {
|
|
60
|
-
await this.
|
|
76
|
+
await this.create(aggregate);
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
79
|
|
|
@@ -65,12 +81,8 @@ export class InMemoryRepository<TDomain extends Aggregate<any>>
|
|
|
65
81
|
this.items.delete(aggregate.id.value);
|
|
66
82
|
}
|
|
67
83
|
|
|
68
|
-
async
|
|
69
|
-
this.items.
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async exists(id: Id): Promise<boolean> {
|
|
73
|
-
return this.items.has(id.value);
|
|
84
|
+
async exists(id: string): Promise<boolean> {
|
|
85
|
+
return this.items.has(id);
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
async count(criteria?: Criteria<TDomain>): Promise<number> {
|
package/src/repository/index.ts
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
// ============================================================================
|
|
4
4
|
|
|
5
5
|
// Mapper
|
|
6
|
-
export {
|
|
7
|
-
export type { IMapper } from "./mapper";
|
|
6
|
+
export { Mapper } from "../mapper";
|
|
8
7
|
|
|
9
8
|
// Base implementations
|
|
10
|
-
export { BaseRepository } from "./base-repository";
|
|
9
|
+
export { Repository as BaseRepository } from "./base-repository";
|
|
11
10
|
export { InMemoryRepository } from "./in-memory-repository";
|
|
12
11
|
|
|
13
12
|
// Unit of Work
|
package/src/types/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Aggregate } from "../entity";
|
|
2
|
-
import {
|
|
2
|
+
import { Repository } from "../repository/base-repository";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Transaction context for Unit of Work
|
|
@@ -41,6 +41,6 @@ export interface IUnitOfWork {
|
|
|
41
41
|
* Get repository within transaction context
|
|
42
42
|
*/
|
|
43
43
|
getRepository<TDomain extends Aggregate<any>>(
|
|
44
|
-
repository: new (...args: any[]) =>
|
|
45
|
-
):
|
|
44
|
+
repository: new (...args: any[]) => Repository<TDomain>
|
|
45
|
+
): Repository<TDomain>;
|
|
46
46
|
}
|
package/tests/repository.test.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { Id, Aggregate, Criteria, BaseProps, PaginatedResult } from "../src";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
BaseRepository,
|
|
5
|
-
BaseMapper,
|
|
6
|
-
} from "../src/repository";
|
|
2
|
+
import { InMemoryRepository, Mapper } from "../src/repository";
|
|
3
|
+
import { Repository } from "../src/repository/base-repository";
|
|
7
4
|
|
|
8
5
|
// ============================================================================
|
|
9
6
|
// Test Domain Models
|
|
@@ -73,9 +70,8 @@ type UserPersistence = {
|
|
|
73
70
|
// ============================================================================
|
|
74
71
|
// Mapper Implementation
|
|
75
72
|
// ============================================================================
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
toDomain(persistence: UserPersistence): User {
|
|
73
|
+
class UserToDomainMapper extends Mapper<UserPersistence, User> {
|
|
74
|
+
public build(persistence: UserPersistence): User {
|
|
79
75
|
return User.create({
|
|
80
76
|
id: Id.from(persistence.id),
|
|
81
77
|
name: persistence.name,
|
|
@@ -84,8 +80,10 @@ class UserMapper extends BaseMapper<User, UserPersistence> {
|
|
|
84
80
|
status: persistence.status as "active" | "inactive",
|
|
85
81
|
});
|
|
86
82
|
}
|
|
83
|
+
}
|
|
87
84
|
|
|
88
|
-
|
|
85
|
+
class UserToPersistenceMapper extends Mapper<User, UserPersistence> {
|
|
86
|
+
public build(domain: User): UserPersistence {
|
|
89
87
|
return {
|
|
90
88
|
id: domain.id.value,
|
|
91
89
|
name: domain.name,
|
|
@@ -102,41 +100,51 @@ class UserMapper extends BaseMapper<User, UserPersistence> {
|
|
|
102
100
|
// Mock Repository Implementation
|
|
103
101
|
// ============================================================================
|
|
104
102
|
|
|
105
|
-
class MockUserRepository extends
|
|
103
|
+
class MockUserRepository extends Repository<User> {
|
|
106
104
|
private store: Map<string, UserPersistence> = new Map();
|
|
107
105
|
|
|
108
|
-
constructor(
|
|
109
|
-
|
|
106
|
+
constructor(
|
|
107
|
+
protected readonly mapperToDomain: Mapper<UserPersistence, User>,
|
|
108
|
+
protected readonly mapperToPersistence: Mapper<User, UserPersistence>
|
|
109
|
+
) {
|
|
110
|
+
super();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get model() {
|
|
114
|
+
return "users";
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
|
|
117
|
+
async create(entity: User) {
|
|
118
|
+
const persistence = this.mapperToPersistence.build(entity);
|
|
119
|
+
this.store.set(entity.id.value, persistence);
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
|
|
118
|
-
id
|
|
119
|
-
data: UserPersistence
|
|
120
|
-
): Promise<UserPersistence> {
|
|
121
|
-
this.store.set(id, { ...data, updatedAt: new Date() });
|
|
122
|
-
return data;
|
|
122
|
+
async delete(entity: User): Promise<void> {
|
|
123
|
+
this.store.delete(entity.id.value);
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
this.
|
|
126
|
+
async count(criteria: Criteria<User>): Promise<number> {
|
|
127
|
+
return this.applyCriteria(criteria).then((result) => result.meta.total);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async exists(id: string): Promise<boolean> {
|
|
131
|
+
return this.store.has(id);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
|
|
134
|
+
async find(criteria: Criteria<User>): Promise<PaginatedResult<User>> {
|
|
135
|
+
const result = await this.applyCriteria(criteria);
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async findById(id: string): Promise<User | null> {
|
|
130
140
|
const persistence = this.store.get(id);
|
|
131
141
|
if (!persistence) return null;
|
|
132
|
-
return this.
|
|
142
|
+
return this.mapperToDomain.build(persistence);
|
|
133
143
|
}
|
|
134
144
|
|
|
135
|
-
|
|
136
|
-
const persistence =
|
|
137
|
-
|
|
138
|
-
? this.mapper.toDomainList(persistence)
|
|
139
|
-
: persistence.map((p) => this.mapper.toDomain(p));
|
|
145
|
+
async update(entity: User): Promise<void> {
|
|
146
|
+
const persistence = this.mapperToPersistence.build(entity);
|
|
147
|
+
this.store.set(entity.id.value, persistence);
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
protected async applyCriteria(criteria: Criteria<User>) {
|
|
@@ -183,9 +191,7 @@ class MockUserRepository extends BaseRepository<User, UserPersistence> {
|
|
|
183
191
|
pagination.offset + pagination.limit
|
|
184
192
|
);
|
|
185
193
|
|
|
186
|
-
const domains = this.
|
|
187
|
-
? this.mapper.toDomainList(results)
|
|
188
|
-
: results.map((p) => this.mapper.toDomain(p));
|
|
194
|
+
const domains = results.map((p) => this.mapperToDomain.build(p));
|
|
189
195
|
|
|
190
196
|
return PaginatedResult.create(domains, pagination, total);
|
|
191
197
|
}
|
|
@@ -221,7 +227,10 @@ describe("Repository", () => {
|
|
|
221
227
|
let user3: User;
|
|
222
228
|
|
|
223
229
|
beforeEach(() => {
|
|
224
|
-
repository = new InMemoryRepository<User>(
|
|
230
|
+
repository = new InMemoryRepository<User>(
|
|
231
|
+
new UserToDomainMapper(),
|
|
232
|
+
new UserToPersistenceMapper()
|
|
233
|
+
);
|
|
225
234
|
|
|
226
235
|
user1 = User.create({
|
|
227
236
|
name: "Alice",
|
|
@@ -251,8 +260,8 @@ describe("Repository", () => {
|
|
|
251
260
|
|
|
252
261
|
describe("save and findById", () => {
|
|
253
262
|
it("should save and retrieve user", async () => {
|
|
254
|
-
await repository.
|
|
255
|
-
const found = await repository.findById(user1.id);
|
|
263
|
+
await repository.create(user1);
|
|
264
|
+
const found = await repository.findById(user1.id.value);
|
|
256
265
|
|
|
257
266
|
expect(found).toBeDefined();
|
|
258
267
|
expect(found?.id.equals(user1.id)).toBe(true);
|
|
@@ -260,16 +269,16 @@ describe("Repository", () => {
|
|
|
260
269
|
});
|
|
261
270
|
|
|
262
271
|
it("should return null for non-existent id", async () => {
|
|
263
|
-
const found = await repository.findById(new Id());
|
|
272
|
+
const found = await repository.findById(new Id().value);
|
|
264
273
|
expect(found).toBeNull();
|
|
265
274
|
});
|
|
266
275
|
|
|
267
276
|
it("should update existing user", async () => {
|
|
268
|
-
await repository.
|
|
277
|
+
await repository.create(user1);
|
|
269
278
|
user1.setName("Alice Updated");
|
|
270
|
-
await repository.
|
|
279
|
+
await repository.create(user1);
|
|
271
280
|
|
|
272
|
-
const found = await repository.findById(user1.id);
|
|
281
|
+
const found = await repository.findById(user1.id.value);
|
|
273
282
|
expect(found?.name).toBe("Alice Updated");
|
|
274
283
|
});
|
|
275
284
|
});
|
|
@@ -281,18 +290,18 @@ describe("Repository", () => {
|
|
|
281
290
|
});
|
|
282
291
|
|
|
283
292
|
it("should return all users", async () => {
|
|
284
|
-
await repository.
|
|
285
|
-
await repository.
|
|
286
|
-
await repository.
|
|
293
|
+
await repository.create(user1);
|
|
294
|
+
await repository.create(user2);
|
|
295
|
+
await repository.create(user3);
|
|
287
296
|
|
|
288
297
|
const users = await repository.findAll();
|
|
289
298
|
expect(users).toHaveLength(3);
|
|
290
299
|
});
|
|
291
300
|
|
|
292
301
|
it("should return users matching criteria", async () => {
|
|
293
|
-
await repository.
|
|
294
|
-
await repository.
|
|
295
|
-
await repository.
|
|
302
|
+
await repository.create(user1);
|
|
303
|
+
await repository.create(user2);
|
|
304
|
+
await repository.create(user3);
|
|
296
305
|
|
|
297
306
|
const criteria = Criteria.create<User>().whereEquals(
|
|
298
307
|
"status",
|
|
@@ -307,9 +316,9 @@ describe("Repository", () => {
|
|
|
307
316
|
|
|
308
317
|
describe("find with Criteria", () => {
|
|
309
318
|
beforeEach(async () => {
|
|
310
|
-
await repository.
|
|
311
|
-
await repository.
|
|
312
|
-
await repository.
|
|
319
|
+
await repository.create(user1);
|
|
320
|
+
await repository.create(user2);
|
|
321
|
+
await repository.create(user3);
|
|
313
322
|
});
|
|
314
323
|
|
|
315
324
|
it("should filter by equals", async () => {
|
|
@@ -364,8 +373,8 @@ describe("Repository", () => {
|
|
|
364
373
|
|
|
365
374
|
describe("findOne", () => {
|
|
366
375
|
it("should find first matching user", async () => {
|
|
367
|
-
await repository.
|
|
368
|
-
await repository.
|
|
376
|
+
await repository.create(user1);
|
|
377
|
+
await repository.create(user2);
|
|
369
378
|
|
|
370
379
|
const criteria = Criteria.create<User>().whereEquals(
|
|
371
380
|
"status",
|
|
@@ -390,40 +399,40 @@ describe("Repository", () => {
|
|
|
390
399
|
|
|
391
400
|
describe("delete", () => {
|
|
392
401
|
it("should delete by aggregate", async () => {
|
|
393
|
-
await repository.
|
|
402
|
+
await repository.create(user1);
|
|
394
403
|
await repository.delete(user1);
|
|
395
404
|
|
|
396
|
-
const found = await repository.findById(user1.id);
|
|
405
|
+
const found = await repository.findById(user1.id.value);
|
|
397
406
|
expect(found).toBeNull();
|
|
398
407
|
});
|
|
399
408
|
|
|
400
409
|
it("should delete by id", async () => {
|
|
401
|
-
await repository.
|
|
402
|
-
await repository.
|
|
410
|
+
await repository.create(user1);
|
|
411
|
+
await repository.delete(user1);
|
|
403
412
|
|
|
404
|
-
const found = await repository.findById(user1.id);
|
|
413
|
+
const found = await repository.findById(user1.id.value);
|
|
405
414
|
expect(found).toBeNull();
|
|
406
415
|
});
|
|
407
416
|
});
|
|
408
417
|
|
|
409
418
|
describe("exists", () => {
|
|
410
419
|
it("should return true when user exists", async () => {
|
|
411
|
-
await repository.
|
|
412
|
-
const exists = await repository.exists(user1.id);
|
|
420
|
+
await repository.create(user1);
|
|
421
|
+
const exists = await repository.exists(user1.id.value);
|
|
413
422
|
expect(exists).toBe(true);
|
|
414
423
|
});
|
|
415
424
|
|
|
416
425
|
it("should return false when user does not exist", async () => {
|
|
417
|
-
const exists = await repository.exists(new Id());
|
|
426
|
+
const exists = await repository.exists(new Id().value);
|
|
418
427
|
expect(exists).toBe(false);
|
|
419
428
|
});
|
|
420
429
|
});
|
|
421
430
|
|
|
422
431
|
describe("count", () => {
|
|
423
432
|
beforeEach(async () => {
|
|
424
|
-
await repository.
|
|
425
|
-
await repository.
|
|
426
|
-
await repository.
|
|
433
|
+
await repository.create(user1);
|
|
434
|
+
await repository.create(user2);
|
|
435
|
+
await repository.create(user3);
|
|
427
436
|
});
|
|
428
437
|
|
|
429
438
|
it("should count all users", async () => {
|
|
@@ -443,7 +452,7 @@ describe("Repository", () => {
|
|
|
443
452
|
|
|
444
453
|
describe("saveMany", () => {
|
|
445
454
|
it("should save multiple users", async () => {
|
|
446
|
-
await repository.
|
|
455
|
+
await repository.createMany([user1, user2, user3]);
|
|
447
456
|
|
|
448
457
|
const users = await repository.findAll();
|
|
449
458
|
expect(users).toHaveLength(3);
|
|
@@ -456,7 +465,10 @@ describe("Repository", () => {
|
|
|
456
465
|
let user: User;
|
|
457
466
|
|
|
458
467
|
beforeEach(() => {
|
|
459
|
-
repository = new MockUserRepository(
|
|
468
|
+
repository = new MockUserRepository(
|
|
469
|
+
new UserToDomainMapper(),
|
|
470
|
+
new UserToPersistenceMapper()
|
|
471
|
+
);
|
|
460
472
|
user = User.create({
|
|
461
473
|
name: "John",
|
|
462
474
|
email: "john@example.com",
|
|
@@ -471,15 +483,15 @@ describe("Repository", () => {
|
|
|
471
483
|
|
|
472
484
|
it("should save new user (insert)", async () => {
|
|
473
485
|
expect(user.isNew).toBe(true);
|
|
474
|
-
await repository.
|
|
486
|
+
await repository.create(user);
|
|
475
487
|
|
|
476
|
-
const found = await repository.findById(user.id);
|
|
488
|
+
const found = await repository.findById(user.id.value);
|
|
477
489
|
expect(found).toBeDefined();
|
|
478
490
|
expect(found?.name).toBe("John");
|
|
479
491
|
});
|
|
480
492
|
|
|
481
493
|
it("should update existing user", async () => {
|
|
482
|
-
await repository.
|
|
494
|
+
await repository.create(user);
|
|
483
495
|
|
|
484
496
|
// Simulate existing user
|
|
485
497
|
const existingUser = User.create({
|
|
@@ -490,16 +502,16 @@ describe("Repository", () => {
|
|
|
490
502
|
status: "inactive",
|
|
491
503
|
});
|
|
492
504
|
|
|
493
|
-
await repository.
|
|
505
|
+
await repository.create(existingUser);
|
|
494
506
|
|
|
495
|
-
const found = await repository.findById(user.id);
|
|
507
|
+
const found = await repository.findById(user.id.value);
|
|
496
508
|
expect(found?.name).toBe("John Updated");
|
|
497
509
|
expect(found?.age).toBe(29);
|
|
498
510
|
});
|
|
499
511
|
|
|
500
512
|
it("should convert between domain and persistence", async () => {
|
|
501
|
-
await repository.
|
|
502
|
-
const found = await repository.findById(user.id);
|
|
513
|
+
await repository.create(user);
|
|
514
|
+
const found = await repository.findById(user.id.value);
|
|
503
515
|
|
|
504
516
|
// Should be a domain object
|
|
505
517
|
expect(found).toBeInstanceOf(User);
|
|
@@ -520,7 +532,7 @@ describe("Repository", () => {
|
|
|
520
532
|
status: "inactive",
|
|
521
533
|
});
|
|
522
534
|
|
|
523
|
-
await Promise.all([repository.
|
|
535
|
+
await Promise.all([repository.create(user1), repository.create(user2)]);
|
|
524
536
|
|
|
525
537
|
const result = await repository.find(
|
|
526
538
|
Criteria.create<User>()
|
|
@@ -535,10 +547,12 @@ describe("Repository", () => {
|
|
|
535
547
|
});
|
|
536
548
|
|
|
537
549
|
describe("Mapper", () => {
|
|
538
|
-
let
|
|
550
|
+
let toDomainMapper: UserToDomainMapper;
|
|
551
|
+
let toPersistenceMapper: UserToPersistenceMapper;
|
|
539
552
|
|
|
540
553
|
beforeEach(() => {
|
|
541
|
-
|
|
554
|
+
toDomainMapper = new UserToDomainMapper();
|
|
555
|
+
toPersistenceMapper = new UserToPersistenceMapper();
|
|
542
556
|
});
|
|
543
557
|
|
|
544
558
|
it("should convert from persistence to domain", () => {
|
|
@@ -552,7 +566,7 @@ describe("Repository", () => {
|
|
|
552
566
|
updatedAt: new Date(),
|
|
553
567
|
};
|
|
554
568
|
|
|
555
|
-
const domain =
|
|
569
|
+
const domain = toDomainMapper.build(persistence);
|
|
556
570
|
|
|
557
571
|
expect(domain).toBeInstanceOf(User);
|
|
558
572
|
expect(domain.id.value).toBe("123");
|
|
@@ -571,7 +585,7 @@ describe("Repository", () => {
|
|
|
571
585
|
status: "active",
|
|
572
586
|
});
|
|
573
587
|
|
|
574
|
-
const persistence =
|
|
588
|
+
const persistence = toPersistenceMapper.build(domain);
|
|
575
589
|
|
|
576
590
|
expect(persistence.id).toBe("123");
|
|
577
591
|
expect(persistence.name).toBe("John");
|
|
@@ -604,7 +618,7 @@ describe("Repository", () => {
|
|
|
604
618
|
},
|
|
605
619
|
];
|
|
606
620
|
|
|
607
|
-
const domainList =
|
|
621
|
+
const domainList = persistenceList.map((p) => toDomainMapper.build(p));
|
|
608
622
|
|
|
609
623
|
expect(domainList).toHaveLength(2);
|
|
610
624
|
expect(domainList[0]).toBeInstanceOf(User);
|
|
@@ -630,7 +644,7 @@ describe("Repository", () => {
|
|
|
630
644
|
}),
|
|
631
645
|
];
|
|
632
646
|
|
|
633
|
-
const persistenceList =
|
|
647
|
+
const persistenceList = domainList.map((d) => toPersistenceMapper.build(d));
|
|
634
648
|
|
|
635
649
|
expect(persistenceList).toHaveLength(2);
|
|
636
650
|
expect(persistenceList[0].id).toBe("1");
|