@woltz/rich-domain 1.3.1 → 1.3.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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -11
- package/eslint.config.js +0 -57
- package/jest.config.js +0 -21
- package/src/aggregate-changes.ts +0 -444
- package/src/base-entity.ts +0 -410
- package/src/change-tracker.ts +0 -1123
- package/src/constants.ts +0 -81
- package/src/criteria.ts +0 -521
- package/src/crypto.ts +0 -31
- package/src/domain-event-bus.ts +0 -152
- package/src/domain-event.ts +0 -49
- package/src/entity-changes.ts +0 -146
- package/src/entity-schema-registry.ts +0 -505
- package/src/entity.ts +0 -5
- package/src/exceptions.ts +0 -435
- package/src/id.ts +0 -98
- package/src/index.ts +0 -52
- package/src/mapper.ts +0 -6
- package/src/paginated-result.ts +0 -250
- package/src/repository/base-repository.ts +0 -33
- package/src/repository/index.ts +0 -3
- package/src/repository/unit-of-work.ts +0 -76
- package/src/types/change-tracker.ts +0 -264
- package/src/types/criteria.ts +0 -159
- package/src/types/domain-event.ts +0 -38
- package/src/types/domain.ts +0 -33
- package/src/types/index.ts +0 -7
- package/src/types/standard-schema.ts +0 -19
- package/src/types/unit-of-work.ts +0 -46
- package/src/types/utils.ts +0 -20
- package/src/utils/criteria-operator-validation.ts +0 -209
- package/src/utils/helpers.ts +0 -34
- package/src/validation-error.ts +0 -141
- package/src/value-object.ts +0 -249
package/src/paginated-result.ts
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import { Id } from "./id";
|
|
2
|
-
import type { Criteria } from "./criteria";
|
|
3
|
-
import type { Pagination, PaginationMeta, Filter } from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Infers the JSON result type from T
|
|
7
|
-
* - If T has toJSON(), returns its return type
|
|
8
|
-
* - Otherwise returns T as-is
|
|
9
|
-
*/
|
|
10
|
-
type InferJsonResult<T> = T extends { toJSON(): infer R } ? R : T;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Type for the serialized result of PaginatedResult.toJSON()
|
|
14
|
-
*/
|
|
15
|
-
export type PaginatedJsonResult<T> = {
|
|
16
|
-
data: InferJsonResult<T>[];
|
|
17
|
-
meta: PaginationMeta;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export class PaginatedResult<T> {
|
|
21
|
-
constructor(
|
|
22
|
-
public readonly data: T[],
|
|
23
|
-
public readonly meta: PaginationMeta
|
|
24
|
-
) {}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Creates a PaginatedResult with calculated metadata
|
|
28
|
-
*/
|
|
29
|
-
static create<T>(
|
|
30
|
-
data: T[],
|
|
31
|
-
pagination: Pagination,
|
|
32
|
-
total: number
|
|
33
|
-
): PaginatedResult<T> {
|
|
34
|
-
const meta = this.createMeta(pagination, total);
|
|
35
|
-
return new PaginatedResult(data, meta);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Creates pagination metadata from total count
|
|
40
|
-
*/
|
|
41
|
-
static createMeta(pagination: Pagination, total: number): PaginationMeta {
|
|
42
|
-
const totalPages = Math.ceil(total / pagination.limit);
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
page: pagination.page,
|
|
46
|
-
limit: pagination.limit,
|
|
47
|
-
total,
|
|
48
|
-
totalPages,
|
|
49
|
-
hasNext: pagination.page < totalPages,
|
|
50
|
-
hasPrevious: pagination.page > 1,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Applies criteria to an in-memory array (useful for testing)
|
|
56
|
-
*/
|
|
57
|
-
static fromArray<T>(items: T[], criteria: Criteria<T>): PaginatedResult<T> {
|
|
58
|
-
let result = [...items];
|
|
59
|
-
let total = result.length;
|
|
60
|
-
|
|
61
|
-
for (const filter of criteria.getFilters()) {
|
|
62
|
-
result = result.filter((item) => applyFilter(item, filter));
|
|
63
|
-
total = result.length;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (const order of criteria.getOrders().reverse()) {
|
|
67
|
-
result.sort((a, b) => {
|
|
68
|
-
const aVal = getNestedValue(a, order.field);
|
|
69
|
-
const bVal = getNestedValue(b, order.field);
|
|
70
|
-
|
|
71
|
-
let comparison = 0;
|
|
72
|
-
if (aVal < bVal) comparison = -1;
|
|
73
|
-
if (aVal > bVal) comparison = 1;
|
|
74
|
-
|
|
75
|
-
return order.direction === "desc" ? -comparison : comparison;
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const pagination = criteria.getPagination();
|
|
80
|
-
result = result.slice(
|
|
81
|
-
pagination.offset,
|
|
82
|
-
pagination.offset + pagination.limit
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const search = criteria.getSearch();
|
|
86
|
-
|
|
87
|
-
if (search) {
|
|
88
|
-
result = result.filter((item) => {
|
|
89
|
-
const values = Object.values(item as Record<string, unknown>);
|
|
90
|
-
return values.some((val) =>
|
|
91
|
-
String(val).toLowerCase().includes(search.toLowerCase())
|
|
92
|
-
);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return PaginatedResult.create(result, pagination, total);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Converts the result to JSON, deeply serializing all entities/aggregates/value objects
|
|
101
|
-
* - Entities/Aggregates → calls toJSON() recursively
|
|
102
|
-
* - Value Objects → calls toJSON()
|
|
103
|
-
* - Id → converts to string
|
|
104
|
-
* - Arrays → maps recursively
|
|
105
|
-
* - Plain objects → serializes properties recursively
|
|
106
|
-
* - Primitives → returns as-is
|
|
107
|
-
*/
|
|
108
|
-
toJSON(): PaginatedJsonResult<T> {
|
|
109
|
-
return {
|
|
110
|
-
data: this.data.map((item) =>
|
|
111
|
-
this.deepSerialize(item)
|
|
112
|
-
) as InferJsonResult<T>[],
|
|
113
|
-
meta: this.meta,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Deep serialization logic (similar to BaseEntity.deepToJson)
|
|
119
|
-
*/
|
|
120
|
-
private deepSerialize(obj: any): any {
|
|
121
|
-
if (obj === null || obj === undefined) return obj;
|
|
122
|
-
|
|
123
|
-
if (obj instanceof Id) return obj.value;
|
|
124
|
-
|
|
125
|
-
if (Array.isArray(obj)) {
|
|
126
|
-
return obj.map((item) => this.deepSerialize(item));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (obj && typeof obj.toJSON === "function") {
|
|
130
|
-
return obj.toJSON();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (typeof obj === "object") {
|
|
134
|
-
const result: any = {};
|
|
135
|
-
for (const key in obj) {
|
|
136
|
-
if (obj.hasOwnProperty(key)) {
|
|
137
|
-
result[key] = this.deepSerialize(obj[key]);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return result;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return obj;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Transform each item in the result using a mapper function
|
|
148
|
-
*/
|
|
149
|
-
map<U>(fn: (item: T) => U): PaginatedResult<U> {
|
|
150
|
-
return new PaginatedResult(this.data.map(fn), this.meta);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check if result has no data
|
|
155
|
-
*/
|
|
156
|
-
get isEmpty(): boolean {
|
|
157
|
-
return this.data.length === 0;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Check if there are more pages available
|
|
162
|
-
*/
|
|
163
|
-
get hasMore(): boolean {
|
|
164
|
-
return this.meta.hasNext;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function applyFilter<T>(item: T, filter: Filter): boolean {
|
|
169
|
-
const value = getNestedValue(item, filter.field);
|
|
170
|
-
|
|
171
|
-
const isValueDate = value instanceof Date;
|
|
172
|
-
|
|
173
|
-
const parseValue = (v: any) => {
|
|
174
|
-
if (isValueDate && typeof v === "string") return new Date(v);
|
|
175
|
-
if (isValueDate && typeof v === "number") return new Date(v);
|
|
176
|
-
return v;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
switch (filter.operator) {
|
|
180
|
-
case "equals":
|
|
181
|
-
return value === filter.value;
|
|
182
|
-
|
|
183
|
-
case "notEquals":
|
|
184
|
-
return value !== filter.value;
|
|
185
|
-
|
|
186
|
-
case "greaterThan": {
|
|
187
|
-
const compareTo = parseValue(filter.value);
|
|
188
|
-
return isValueDate ? value > compareTo : value > (filter.value as any);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
case "greaterThanOrEqual": {
|
|
192
|
-
const compareTo = parseValue(filter.value);
|
|
193
|
-
return isValueDate ? value >= compareTo : value >= (filter.value as any);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
case "lessThan":
|
|
197
|
-
const lt = parseValue(filter.value);
|
|
198
|
-
return isValueDate ? value < lt : value < (filter.value as any);
|
|
199
|
-
|
|
200
|
-
case "lessThanOrEqual":
|
|
201
|
-
const lte = parseValue(filter.value);
|
|
202
|
-
return isValueDate ? value <= lte : value <= (filter.value as any);
|
|
203
|
-
|
|
204
|
-
case "contains":
|
|
205
|
-
return String(value)
|
|
206
|
-
.toLowerCase()
|
|
207
|
-
.includes(String(filter.value).toLowerCase());
|
|
208
|
-
|
|
209
|
-
case "startsWith":
|
|
210
|
-
return String(value)
|
|
211
|
-
.toLowerCase()
|
|
212
|
-
.startsWith(String(filter.value).toLowerCase());
|
|
213
|
-
|
|
214
|
-
case "endsWith":
|
|
215
|
-
return String(value)
|
|
216
|
-
.toLowerCase()
|
|
217
|
-
.endsWith(String(filter.value).toLowerCase());
|
|
218
|
-
|
|
219
|
-
case "in":
|
|
220
|
-
return Array.isArray(filter.value) && filter.value.includes(value);
|
|
221
|
-
|
|
222
|
-
case "notIn":
|
|
223
|
-
return Array.isArray(filter.value) && !filter.value.includes(value);
|
|
224
|
-
|
|
225
|
-
case "between":
|
|
226
|
-
if (Array.isArray(filter.value) && filter.value.length === 2) {
|
|
227
|
-
const [min, max] = filter.value.map(parseValue);
|
|
228
|
-
return isValueDate
|
|
229
|
-
? value >= min && value <= max
|
|
230
|
-
: value >= filter.value[0] && value <= filter.value[1];
|
|
231
|
-
}
|
|
232
|
-
return false;
|
|
233
|
-
|
|
234
|
-
case "isNull":
|
|
235
|
-
return value === null || value === undefined;
|
|
236
|
-
|
|
237
|
-
case "isNotNull":
|
|
238
|
-
return value !== null && value !== undefined;
|
|
239
|
-
|
|
240
|
-
default:
|
|
241
|
-
return true;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function getNestedValue(obj: any, path: string): any {
|
|
246
|
-
return path.split(".").reduce((current, key) => {
|
|
247
|
-
if (current === null || current === undefined) return undefined;
|
|
248
|
-
return current[key];
|
|
249
|
-
}, obj);
|
|
250
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { Aggregate } from "../entity";
|
|
2
|
-
import type { Criteria } from "../criteria";
|
|
3
|
-
import { PaginatedResult } from "../paginated-result";
|
|
4
|
-
import { Mapper } from "../mapper";
|
|
5
|
-
|
|
6
|
-
export abstract class ReadRepository<Agg extends Aggregate<any>> {
|
|
7
|
-
abstract find(criteria?: Criteria<Agg>): Promise<PaginatedResult<Agg>>;
|
|
8
|
-
abstract findById(id: string): Promise<Agg | null>;
|
|
9
|
-
abstract count(criteria?: Criteria<Agg>): Promise<number>;
|
|
10
|
-
abstract exists(id: string): Promise<boolean>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export abstract class WriteRepository<Agg extends Aggregate<any>> {
|
|
14
|
-
abstract save(entity: Agg): Promise<void>;
|
|
15
|
-
abstract delete(entity: Agg): Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export abstract class WriteAndRead<Agg extends Aggregate<any>> {
|
|
19
|
-
abstract find(criteria?: Criteria<Agg>): Promise<PaginatedResult<Agg>>;
|
|
20
|
-
abstract findById(id: string): Promise<Agg | null>;
|
|
21
|
-
abstract save(entity: Agg): Promise<void>;
|
|
22
|
-
abstract delete(entity: Agg): Promise<void>;
|
|
23
|
-
abstract count(criteria?: Criteria<Agg>): Promise<number>;
|
|
24
|
-
abstract exists(id: string): Promise<boolean>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export abstract class Repository<
|
|
28
|
-
TDomain extends Aggregate<any>
|
|
29
|
-
> extends WriteAndRead<TDomain> {
|
|
30
|
-
protected abstract readonly mapperToDomain: Mapper<unknown, TDomain>;
|
|
31
|
-
protected abstract readonly mapperToPersistence: Mapper<TDomain, unknown>;
|
|
32
|
-
protected abstract get model(): any;
|
|
33
|
-
}
|
package/src/repository/index.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import type { IUnitOfWork, TransactionContext } from "../types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Abstract Unit of Work
|
|
5
|
-
* Provides transaction management across multiple repositories
|
|
6
|
-
*/
|
|
7
|
-
export abstract class UnitOfWork implements IUnitOfWork {
|
|
8
|
-
protected currentContext: TransactionContext | null = null;
|
|
9
|
-
protected repositoryCache: Map<string, any> = new Map();
|
|
10
|
-
|
|
11
|
-
abstract begin(): Promise<TransactionContext>;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Execute work within a transaction
|
|
15
|
-
* Auto-commits on success, rolls back on error
|
|
16
|
-
*/
|
|
17
|
-
async transaction<T>(
|
|
18
|
-
work: (ctx: TransactionContext) => Promise<T>
|
|
19
|
-
): Promise<T> {
|
|
20
|
-
const ctx = await this.begin();
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const result = await work(ctx);
|
|
24
|
-
await ctx.commit();
|
|
25
|
-
return result;
|
|
26
|
-
} catch (error) {
|
|
27
|
-
if (ctx.isActive()) {
|
|
28
|
-
await ctx.rollback();
|
|
29
|
-
}
|
|
30
|
-
throw error;
|
|
31
|
-
} finally {
|
|
32
|
-
this.currentContext = null;
|
|
33
|
-
this.repositoryCache.clear();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get repository instance (cached per transaction)
|
|
39
|
-
*/
|
|
40
|
-
getRepository<TRepo>(RepositoryClass: new (...args: any[]) => TRepo): TRepo {
|
|
41
|
-
const key = RepositoryClass.name;
|
|
42
|
-
|
|
43
|
-
if (this.repositoryCache.has(key)) {
|
|
44
|
-
return this.repositoryCache.get(key);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const repo = this.createRepository(RepositoryClass);
|
|
48
|
-
this.repositoryCache.set(key, repo);
|
|
49
|
-
return repo;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Create repository instance - implement in subclass
|
|
54
|
-
*/
|
|
55
|
-
protected abstract createRepository<TRepo>(
|
|
56
|
-
RepositoryClass: new (...args: any[]) => TRepo
|
|
57
|
-
): TRepo;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Base Transaction Context
|
|
62
|
-
*/
|
|
63
|
-
export abstract class BaseTransactionContext implements TransactionContext {
|
|
64
|
-
protected _isActive = true;
|
|
65
|
-
|
|
66
|
-
abstract commit(): Promise<void>;
|
|
67
|
-
abstract rollback(): Promise<void>;
|
|
68
|
-
|
|
69
|
-
isActive(): boolean {
|
|
70
|
-
return this._isActive;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
protected markInactive(): void {
|
|
74
|
-
this._isActive = false;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import { Entity } from "../entity";
|
|
2
|
-
import { ValueObject } from "../value-object";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Base operation with common information.
|
|
6
|
-
*/
|
|
7
|
-
export interface BaseOperation {
|
|
8
|
-
/** Entity name in the domain (e.g., 'User', 'Post', 'Comment') */
|
|
9
|
-
entity: string;
|
|
10
|
-
/** Depth in the aggregate tree (0 = root, 1 = direct children, etc.) */
|
|
11
|
-
depth: number;
|
|
12
|
-
/** Name of the relation field in the parent entity (e.g., 'tags', 'comments') */
|
|
13
|
-
relationField?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create operation.
|
|
18
|
-
*/
|
|
19
|
-
export interface CreateOperation<T = any> extends BaseOperation {
|
|
20
|
-
type: "create";
|
|
21
|
-
/** Entity data to be created */
|
|
22
|
-
data: T;
|
|
23
|
-
/** Parent ID (for FK) */
|
|
24
|
-
parentId?: string;
|
|
25
|
-
/** Parent entity name */
|
|
26
|
-
parentEntity?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Update operation.
|
|
31
|
-
*/
|
|
32
|
-
export interface UpdateOperation<T = any> extends BaseOperation {
|
|
33
|
-
type: "update";
|
|
34
|
-
/** Entity ID */
|
|
35
|
-
id: string;
|
|
36
|
-
/** Current entity instance */
|
|
37
|
-
data: T;
|
|
38
|
-
/** Only fields that have changed */
|
|
39
|
-
changedFields: Record<string, any>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Delete operation.
|
|
44
|
-
*/
|
|
45
|
-
export interface DeleteOperation<T = any> extends BaseOperation {
|
|
46
|
-
type: "delete";
|
|
47
|
-
/** Entity ID to be deleted */
|
|
48
|
-
id: string;
|
|
49
|
-
/** Entity data (for reference) */
|
|
50
|
-
data: T;
|
|
51
|
-
/** Parent entity name */
|
|
52
|
-
parentEntity?: string;
|
|
53
|
-
/** Parent ID */
|
|
54
|
-
parentId?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Union of all possible operations.
|
|
59
|
-
*/
|
|
60
|
-
export type Operation<T = any> =
|
|
61
|
-
| CreateOperation<T>
|
|
62
|
-
| UpdateOperation<T>
|
|
63
|
-
| DeleteOperation<T>;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Item for batch creation.
|
|
67
|
-
*/
|
|
68
|
-
export interface BatchCreateItem<T = any> {
|
|
69
|
-
/** Entity data */
|
|
70
|
-
data: T;
|
|
71
|
-
/** Parent ID (for FK) */
|
|
72
|
-
parentId?: string;
|
|
73
|
-
/** Parent entity name */
|
|
74
|
-
parentEntity?: string;
|
|
75
|
-
/** Relation field name */
|
|
76
|
-
relationField?: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Item for batch update.
|
|
81
|
-
*/
|
|
82
|
-
export interface BatchUpdateItem {
|
|
83
|
-
/** Entity ID */
|
|
84
|
-
id: string;
|
|
85
|
-
/** Fields that have changed */
|
|
86
|
-
changedFields: Record<string, any>;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Item for batch delete.
|
|
91
|
-
*/
|
|
92
|
-
export interface BatchDeleteItem {
|
|
93
|
-
/** Entity ID */
|
|
94
|
-
id: string;
|
|
95
|
-
/** Relation field name */
|
|
96
|
-
relationField?: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Grouped and ordered operations for batch execution.
|
|
101
|
-
*/
|
|
102
|
-
export interface BatchOperations {
|
|
103
|
-
/**
|
|
104
|
-
* Deletes grouped by entity, ordered by depth descending (leaf → root).
|
|
105
|
-
*/
|
|
106
|
-
deletes: Array<{
|
|
107
|
-
entity: string;
|
|
108
|
-
depth: number;
|
|
109
|
-
ids: string[];
|
|
110
|
-
parentId?: string;
|
|
111
|
-
/** Relation field name (for determining owned vs reference) */
|
|
112
|
-
relationField?: string;
|
|
113
|
-
/** Parent entity name */
|
|
114
|
-
parentEntity?: string;
|
|
115
|
-
/** Individual items with their relation fields (when mixed) */
|
|
116
|
-
items?: BatchDeleteItem[];
|
|
117
|
-
}>;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Creates grouped by entity, ordered by depth ascending (root → leaf).
|
|
121
|
-
*/
|
|
122
|
-
creates: Array<{
|
|
123
|
-
entity: string;
|
|
124
|
-
depth: number;
|
|
125
|
-
items: BatchCreateItem[];
|
|
126
|
-
/** Relation field name (for determining owned vs reference) */
|
|
127
|
-
relationField?: string;
|
|
128
|
-
/** Parent entity name */
|
|
129
|
-
parentEntity?: string;
|
|
130
|
-
}>;
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Updates grouped by entity.
|
|
134
|
-
*/
|
|
135
|
-
updates: Array<{
|
|
136
|
-
entity: string;
|
|
137
|
-
items: BatchUpdateItem[];
|
|
138
|
-
}>;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Changes detected in a collection (1:N).
|
|
143
|
-
*/
|
|
144
|
-
export interface CollectionChanges<T = any> {
|
|
145
|
-
/** Created items */
|
|
146
|
-
created: T[];
|
|
147
|
-
/** Updated items with their changes */
|
|
148
|
-
updated: Array<{
|
|
149
|
-
entity: T;
|
|
150
|
-
changes: Record<string, { from: any; to: any }>;
|
|
151
|
-
}>;
|
|
152
|
-
/** Deleted items */
|
|
153
|
-
deleted: T[];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Possible states for a 1:1 relationship.
|
|
158
|
-
*/
|
|
159
|
-
export type EntityChangeState =
|
|
160
|
-
| "created" // null → Entity
|
|
161
|
-
| "updated" // Entity(id:1) → Entity(id:1) with changes
|
|
162
|
-
| "deleted" // Entity → null
|
|
163
|
-
| "replaced" // Entity(id:1) → Entity(id:2)
|
|
164
|
-
| "unchanged"; // No changes
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Change in a 1:1 entity relationship.
|
|
168
|
-
*/
|
|
169
|
-
export interface EntityChange<T = any> {
|
|
170
|
-
/** State of the change */
|
|
171
|
-
state: EntityChangeState;
|
|
172
|
-
/** Current entity (null if deleted) */
|
|
173
|
-
current: T | null;
|
|
174
|
-
/** Previous entity (null if created) */
|
|
175
|
-
previous: T | null;
|
|
176
|
-
/** Field changes (if state === 'updated') */
|
|
177
|
-
changes?: Record<string, { from: any; to: any }>;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Change in a primitive field.
|
|
182
|
-
*/
|
|
183
|
-
export interface FieldChange<T = any> {
|
|
184
|
-
from: T;
|
|
185
|
-
to: T;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Extracts the props type from an Entity or ValueObject.
|
|
190
|
-
*/
|
|
191
|
-
export type ExtractProps<T> = T extends Entity<infer P>
|
|
192
|
-
? P
|
|
193
|
-
: T extends ValueObject<infer P>
|
|
194
|
-
? P
|
|
195
|
-
: never;
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Keys of primitive properties (not Entity, ValueObject or Array).
|
|
199
|
-
*/
|
|
200
|
-
export type PrimitiveKeys<T> = {
|
|
201
|
-
[K in keyof T]: T[K] extends
|
|
202
|
-
| Entity<any>
|
|
203
|
-
| ValueObject<any>
|
|
204
|
-
| Array<any>
|
|
205
|
-
| undefined
|
|
206
|
-
? never
|
|
207
|
-
: K;
|
|
208
|
-
}[keyof T];
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Keys of collections (arrays).
|
|
212
|
-
*/
|
|
213
|
-
export type CollectionKeys<T> = {
|
|
214
|
-
[K in keyof T]: T[K] extends Array<any> ? K : never;
|
|
215
|
-
}[keyof T];
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Keys of single entities (1:1).
|
|
219
|
-
*/
|
|
220
|
-
export type SingleEntityKeys<T> = {
|
|
221
|
-
[K in keyof T]: T[K] extends Entity<any> | ValueObject<any> | null | undefined
|
|
222
|
-
? T[K] extends Array<any>
|
|
223
|
-
? never
|
|
224
|
-
: K
|
|
225
|
-
: never;
|
|
226
|
-
}[keyof T];
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Change history entry.
|
|
230
|
-
*/
|
|
231
|
-
export interface HistoryEntry {
|
|
232
|
-
path: string;
|
|
233
|
-
previousValue: any;
|
|
234
|
-
currentValue: any;
|
|
235
|
-
timestamp: number;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Metadata from a tracked entity/VO.
|
|
240
|
-
*/
|
|
241
|
-
export interface TrackedEntityMetadata {
|
|
242
|
-
/** Entity name */
|
|
243
|
-
entityName: string;
|
|
244
|
-
/** Depth in the tree */
|
|
245
|
-
depth: number;
|
|
246
|
-
/** Parent ID */
|
|
247
|
-
parentId?: string;
|
|
248
|
-
/** Parent entity name */
|
|
249
|
-
parentEntity?: string;
|
|
250
|
-
/** Path in the object (e.g., 'posts[0].comments[1]') */
|
|
251
|
-
path: string;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export interface TrackedItem {
|
|
255
|
-
entity: any;
|
|
256
|
-
metadata: TrackedEntityMetadata;
|
|
257
|
-
originalState: any;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export interface ArrayState {
|
|
261
|
-
cloned: any[];
|
|
262
|
-
original: any[];
|
|
263
|
-
metadata: TrackedEntityMetadata;
|
|
264
|
-
}
|