@woltz/rich-domain 0.2.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +37 -20
- package/dist/base-entity.d.ts +1 -1
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +21 -15
- package/dist/base-entity.js.map +1 -1
- package/dist/constants.js +4 -1
- package/dist/constants.js.map +1 -1
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +7 -3
- package/dist/criteria.js.map +1 -1
- package/dist/deep-proxy.d.ts +3 -1
- package/dist/deep-proxy.d.ts.map +1 -1
- package/dist/deep-proxy.js +110 -33
- package/dist/deep-proxy.js.map +1 -1
- package/dist/domain-event-bus.js +7 -2
- package/dist/domain-event-bus.js.map +1 -1
- package/dist/domain-event.js +7 -3
- package/dist/domain-event.js.map +1 -1
- package/dist/entity.js +8 -3
- package/dist/entity.js.map +1 -1
- package/dist/id.d.ts +3 -3
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +10 -6
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -16
- 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 +7 -0
- package/dist/mapper.js.map +1 -0
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +7 -6
- 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 +14 -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 +24 -12
- package/dist/repository/in-memory-repository.js.map +1 -1
- package/dist/repository/index.d.ts +2 -39
- package/dist/repository/index.d.ts.map +1 -1
- package/dist/repository/index.js +26 -40
- package/dist/repository/index.js.map +1 -1
- package/dist/repository/unit-of-work.js +9 -3
- package/dist/repository/unit-of-work.js.map +1 -1
- package/dist/types/criteria.d.ts +2 -2
- package/dist/types/criteria.d.ts.map +1 -1
- package/dist/types/criteria.js +4 -1
- package/dist/types/criteria.js.map +1 -1
- package/dist/types/domain.js +2 -1
- package/dist/types/history-tracker.js +2 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +22 -7
- package/dist/types/index.js.map +1 -1
- package/dist/types/standard-schema.js +2 -1
- package/dist/types/unit-of-work.d.ts +2 -2
- package/dist/types/unit-of-work.d.ts.map +1 -1
- package/dist/types/unit-of-work.js +2 -1
- package/dist/types/utils.js +2 -1
- package/dist/validation-error.js +9 -3
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.js +9 -5
- package/dist/value-object.js.map +1 -1
- package/package.json +1 -1
- package/src/base-entity.ts +3 -2
- package/src/criteria.ts +2 -2
- package/src/deep-proxy.ts +435 -339
- package/src/id.ts +4 -4
- package/src/index.ts +2 -3
- 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 -40
- package/src/types/criteria.ts +2 -2
- package/src/types/index.ts +0 -1
- package/src/types/unit-of-work.ts +3 -3
- package/tests/entity-validation.test.ts +1 -1
- package/tests/history-tracker.spec.ts +57 -17
- package/tests/id.test.ts +341 -341
- package/tests/repository.test.ts +95 -79
- package/tests/to-json.test.ts +103 -91
- package/tests/value-objects.test.ts +52 -52
- package/tsconfig.json +2 -2
- package/dist/filtering.d.ts +0 -107
- package/dist/filtering.d.ts.map +0 -1
- package/dist/filtering.js +0 -202
- package/dist/filtering.js.map +0 -1
- package/dist/ordering.d.ts +0 -93
- package/dist/ordering.d.ts.map +0 -1
- package/dist/ordering.js +0 -154
- package/dist/ordering.js.map +0 -1
- package/dist/pagination.d.ts +0 -218
- package/dist/pagination.d.ts.map +0 -1
- package/dist/pagination.js +0 -281
- package/dist/pagination.js.map +0 -1
- package/dist/repository/mapper.d.ts +0 -56
- package/dist/repository/mapper.d.ts.map +0 -1
- package/dist/repository/mapper.js +0 -15
- package/dist/repository/mapper.js.map +0 -1
- package/dist/repository/types.d.ts +0 -87
- package/dist/repository/types.d.ts.map +0 -1
- package/dist/repository/types.js +0 -6
- package/dist/repository/types.js.map +0 -1
- package/dist/repository.d.ts +0 -2
- package/dist/repository.d.ts.map +0 -1
- package/dist/repository.js +0 -21
- package/dist/repository.js.map +0 -1
- package/dist/specification.d.ts +0 -102
- package/dist/specification.d.ts.map +0 -1
- package/dist/specification.js +0 -187
- package/dist/specification.js.map +0 -1
- package/dist/types/repository.d.ts +0 -43
- package/dist/types/repository.d.ts.map +0 -1
- package/dist/types/repository.js +0 -2
- package/dist/types/repository.js.map +0 -1
- package/dist/types.d.ts +0 -88
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -12
- package/dist/types.js.map +0 -1
- package/src/repository/mapper.ts +0 -74
- package/src/types/repository.ts +0 -51
package/src/id.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
2
|
// ============================================================================
|
|
3
3
|
// Id Class - Smart Identity Management
|
|
4
4
|
// ============================================================================
|
|
@@ -14,11 +14,11 @@ export class Id {
|
|
|
14
14
|
* @example
|
|
15
15
|
* // New entity (generates UUID)
|
|
16
16
|
* const newId = new Id();
|
|
17
|
-
*
|
|
17
|
+
* newuser.isNew() // true
|
|
18
18
|
*
|
|
19
19
|
* // Existing entity (uses provided ID)
|
|
20
20
|
* const existingId = new Id("550e8400-e29b-41d4-a716-446655440000");
|
|
21
|
-
*
|
|
21
|
+
* existinguser.isNew() // false
|
|
22
22
|
*/
|
|
23
23
|
constructor(value?: string) {
|
|
24
24
|
if (value !== undefined) {
|
|
@@ -42,7 +42,7 @@ export class Id {
|
|
|
42
42
|
/**
|
|
43
43
|
* Check if this ID represents a new entity
|
|
44
44
|
*/
|
|
45
|
-
|
|
45
|
+
public isNew(): boolean {
|
|
46
46
|
return this._isNew;
|
|
47
47
|
}
|
|
48
48
|
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { Id } from "./id";
|
|
|
7
7
|
export { BaseEntity } from "./base-entity";
|
|
8
8
|
export { Entity, Aggregate } from "./entity";
|
|
9
9
|
export { ValueObject } from "./value-object";
|
|
10
|
+
export { Mapper } from "./mapper";
|
|
10
11
|
|
|
11
12
|
export * from "./validation-error";
|
|
12
13
|
|
|
@@ -15,14 +16,12 @@ export * from "./domain-event";
|
|
|
15
16
|
|
|
16
17
|
export * from "./domain-event-bus";
|
|
17
18
|
|
|
18
|
-
// Criteria
|
|
19
|
+
// Criteria
|
|
19
20
|
export * from "./criteria";
|
|
20
21
|
export * from "./paginated-result";
|
|
21
22
|
|
|
22
23
|
// Repository
|
|
23
24
|
export * from "./repository";
|
|
24
|
-
// Backward compatibility - re-export InMemoryRepository at top level
|
|
25
|
-
export { InMemoryRepository } from "./repository";
|
|
26
25
|
|
|
27
26
|
// Types
|
|
28
27
|
export * from "./types";
|
package/src/mapper.ts
ADDED
package/src/paginated-result.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// PaginatedResult - Container for paginated data with deep serialization
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
1
|
import { Id } from "./id";
|
|
6
2
|
import type { Criteria } from "./criteria";
|
|
7
3
|
import type { Pagination, PaginationMeta, Filter } from "./types";
|
|
@@ -66,10 +62,7 @@ export class PaginatedResult<T> {
|
|
|
66
62
|
/**
|
|
67
63
|
* Applies criteria to an in-memory array (useful for testing)
|
|
68
64
|
*/
|
|
69
|
-
static fromArray<T>(
|
|
70
|
-
items: T[],
|
|
71
|
-
criteria: Criteria<T>
|
|
72
|
-
): PaginatedResult<T> {
|
|
65
|
+
static fromArray<T>(items: T[], criteria: Criteria<T>): PaginatedResult<T> {
|
|
73
66
|
let result = [...items];
|
|
74
67
|
|
|
75
68
|
// Apply filters
|
|
@@ -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
|
|
9
|
+
export * from "./base-repository";
|
|
11
10
|
export { InMemoryRepository } from "./in-memory-repository";
|
|
12
11
|
|
|
13
12
|
// Unit of Work
|
|
@@ -16,40 +15,3 @@ export {
|
|
|
16
15
|
BaseTransactionContext,
|
|
17
16
|
InMemoryUnitOfWork,
|
|
18
17
|
} from "./unit-of-work";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* QUICK START:
|
|
22
|
-
*
|
|
23
|
-
* 1. For Testing:
|
|
24
|
-
* ```ts
|
|
25
|
-
* import { InMemoryRepository } from 'rich-domain';
|
|
26
|
-
*
|
|
27
|
-
* const userRepo = new InMemoryRepository<User>();
|
|
28
|
-
* await userRepo.save(user);
|
|
29
|
-
* const found = await userRepo.findById(user.id);
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* 2. For Production (Prisma, TypeORM, etc):
|
|
33
|
-
* - Extend BaseRepository
|
|
34
|
-
* - Implement abstract methods
|
|
35
|
-
* - Create a Mapper
|
|
36
|
-
* - See examples/ folder for reference
|
|
37
|
-
*
|
|
38
|
-
* 3. With Criteria:
|
|
39
|
-
* ```ts
|
|
40
|
-
* const result = await userRepo.find(
|
|
41
|
-
* Criteria.create<User>()
|
|
42
|
-
* .whereEquals('status', 'active')
|
|
43
|
-
* .orderByDesc('createdAt')
|
|
44
|
-
* .paginate(1, 10)
|
|
45
|
-
* );
|
|
46
|
-
* ```
|
|
47
|
-
*
|
|
48
|
-
* 4. With Unit of Work:
|
|
49
|
-
* ```ts
|
|
50
|
-
* await uow.transaction(async (ctx) => {
|
|
51
|
-
* const userRepo = uow.getRepository(UserRepository);
|
|
52
|
-
* await userRepo.save(user);
|
|
53
|
-
* });
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
package/src/types/criteria.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Primitive } from "./utils";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const FILTER_OPERATORS = [
|
|
4
4
|
"equals",
|
|
5
5
|
"notEquals",
|
|
6
6
|
"greaterThan",
|
|
@@ -36,7 +36,7 @@ export type PathValue<
|
|
|
36
36
|
? T[P]
|
|
37
37
|
: never;
|
|
38
38
|
|
|
39
|
-
export type FilterOperator = (typeof
|
|
39
|
+
export type FilterOperator = (typeof FILTER_OPERATORS)[number];
|
|
40
40
|
|
|
41
41
|
export interface Filter<TField = string, TValue = unknown> {
|
|
42
42
|
field: TField;
|
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
|
}
|
|
@@ -134,7 +134,7 @@ describe("Rich Domain with Standard Schema Validation", () => {
|
|
|
134
134
|
expect(user.name).toBe("John Doe");
|
|
135
135
|
expect(user.email).toBe("john@example.com");
|
|
136
136
|
expect(user.age).toBe(25);
|
|
137
|
-
expect(user.isNew).toBe(true);
|
|
137
|
+
expect(user.isNew()).toBe(true);
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
it("should throw on invalid email", () => {
|
|
@@ -4,28 +4,68 @@ import { Post, User, Address, Comment } from "./utils";
|
|
|
4
4
|
describe("History Tracker Tests", () => {
|
|
5
5
|
describe("Simple Property Changes", () => {
|
|
6
6
|
it("should track simple property changes", (done) => {
|
|
7
|
-
const
|
|
7
|
+
const user = new User({
|
|
8
8
|
id: new Id("1"),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
name: "John Doe",
|
|
10
|
+
email: "john@example.com",
|
|
11
|
+
posts: [
|
|
12
|
+
new Post({
|
|
13
|
+
id: new Id("1"),
|
|
14
|
+
title: "First Post",
|
|
15
|
+
content: "Hello World",
|
|
16
|
+
likes: 0,
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
address: new Address({
|
|
20
|
+
street: "Main St",
|
|
21
|
+
city: "NYC",
|
|
22
|
+
zipCode: "10001",
|
|
23
|
+
}),
|
|
24
|
+
comments: [],
|
|
12
25
|
});
|
|
13
26
|
|
|
14
|
-
|
|
27
|
+
user.changeEmail("new@example.com");
|
|
28
|
+
user.name = "New Name";
|
|
29
|
+
user.addPost(
|
|
30
|
+
new Post({
|
|
31
|
+
id: new Id("2"),
|
|
32
|
+
title: "Second Post",
|
|
33
|
+
content: "Hello World 2",
|
|
34
|
+
likes: 0,
|
|
35
|
+
})
|
|
36
|
+
);
|
|
15
37
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
function dispatch(entity: User) {
|
|
39
|
+
entity.subscribe({
|
|
40
|
+
email: {
|
|
41
|
+
onChange: ({ previous, current, path }) => {
|
|
42
|
+
expect(previous).toBe("john@example.com");
|
|
43
|
+
expect(current).toBe("new@example.com");
|
|
44
|
+
expect(path).toBe("email");
|
|
45
|
+
},
|
|
24
46
|
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
posts: {
|
|
48
|
+
onChange: ({ toCreate, toUpdate, toDelete }) => {
|
|
49
|
+
expect(toCreate).toHaveLength(1);
|
|
50
|
+
expect(toUpdate).toHaveLength(1);
|
|
51
|
+
expect(toDelete).toHaveLength(0);
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
name: {
|
|
55
|
+
onChange: ({ previous, current, path }) => {
|
|
56
|
+
expect(previous).toBe("John Doe");
|
|
57
|
+
expect(current).toBe("New Name");
|
|
58
|
+
expect(path).toBe("name");
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
user.posts[0].title = "Updated Title";
|
|
64
|
+
dispatch(user);
|
|
65
|
+
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
done();
|
|
68
|
+
}, 100);
|
|
29
69
|
});
|
|
30
70
|
|
|
31
71
|
it("should track multiple property changes", () => {
|