@woltz/rich-domain 1.1.0 → 1.2.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/CHANGELOG.md +87 -21
- package/dist/aggregate-changes.d.ts +164 -0
- package/dist/aggregate-changes.d.ts.map +1 -0
- package/dist/aggregate-changes.js +281 -0
- package/dist/aggregate-changes.js.map +1 -0
- package/dist/base-entity.d.ts +32 -8
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +117 -86
- package/dist/base-entity.js.map +1 -1
- package/dist/criteria.d.ts +31 -15
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +151 -61
- package/dist/criteria.js.map +1 -1
- package/dist/crypto.d.ts +3 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +29 -0
- package/dist/crypto.js.map +1 -0
- package/dist/entity-changes.d.ts +84 -0
- package/dist/entity-changes.d.ts.map +1 -0
- package/dist/entity-changes.js +135 -0
- package/dist/entity-changes.js.map +1 -0
- package/dist/entity-schema-registry.d.ts +148 -0
- package/dist/entity-schema-registry.d.ts.map +1 -0
- package/dist/entity-schema-registry.js +219 -0
- package/dist/entity-schema-registry.js.map +1 -0
- package/dist/history-tracker.d.ts +97 -0
- package/dist/history-tracker.d.ts.map +1 -0
- package/dist/history-tracker.js +805 -0
- package/dist/history-tracker.js.map +1 -0
- package/dist/id.d.ts +11 -10
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +4 -28
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mapper.d.ts +1 -1
- package/dist/mapper.d.ts.map +1 -1
- package/dist/mapper.js.map +1 -1
- 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 +6 -32
- package/dist/repository/base-repository.d.ts.map +1 -1
- package/dist/repository/base-repository.js +0 -27
- package/dist/repository/base-repository.js.map +1 -1
- package/dist/repository/unit-of-work.d.ts +0 -25
- package/dist/repository/unit-of-work.d.ts.map +1 -1
- package/dist/repository/unit-of-work.js +0 -25
- package/dist/repository/unit-of-work.js.map +1 -1
- package/dist/types/change-tracker.d.ts +186 -0
- package/dist/types/change-tracker.d.ts.map +1 -0
- package/dist/types/change-tracker.js +2 -0
- package/dist/types/change-tracker.js.map +1 -0
- package/dist/types/criteria.d.ts +31 -7
- package/dist/types/criteria.d.ts.map +1 -1
- package/dist/types/history-tracker.d.ts +11 -0
- package/dist/types/history-tracker.d.ts.map +1 -1
- package/dist/types/utils.d.ts +0 -1
- package/dist/types/utils.d.ts.map +1 -1
- package/dist/utils/criteria-operator-validation.d.ts +5 -0
- package/dist/utils/criteria-operator-validation.d.ts.map +1 -0
- package/dist/utils/criteria-operator-validation.js +143 -0
- package/dist/utils/criteria-operator-validation.js.map +1 -0
- package/dist/utils/helpers.d.ts +2 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +10 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/validation-error.d.ts.map +1 -1
- package/dist/validation-error.js +0 -3
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.d.ts +57 -8
- package/dist/value-object.d.ts.map +1 -1
- package/dist/value-object.js +49 -21
- package/dist/value-object.js.map +1 -1
- package/eslint.config.js +6 -0
- package/package.json +2 -1
- package/src/aggregate-changes.ts +335 -0
- package/src/base-entity.ts +140 -100
- package/src/criteria.ts +264 -82
- package/src/crypto.ts +31 -0
- package/src/entity-changes.ts +151 -0
- package/src/entity-schema-registry.ts +275 -0
- package/src/history-tracker.ts +1114 -0
- package/src/id.ts +17 -26
- package/src/index.ts +8 -2
- package/src/mapper.ts +4 -1
- package/src/paginated-result.ts +7 -8
- package/src/repository/base-repository.ts +6 -37
- package/src/repository/unit-of-work.ts +0 -25
- package/src/types/change-tracker.ts +221 -0
- package/src/types/criteria.ts +95 -17
- package/src/types/history-tracker.ts +13 -0
- package/src/types/utils.ts +0 -9
- package/src/utils/criteria-operator-validation.ts +171 -0
- package/src/utils/helpers.ts +6 -0
- package/src/validation-error.ts +0 -4
- package/src/value-object.ts +84 -23
- package/tests/aggregate-changes.test.ts +284 -0
- package/tests/criteria.test.ts +366 -90
- package/tests/entity-equality.test.ts +38 -61
- package/tests/entity-schema-registry.test.ts +382 -0
- package/tests/entity-validation.test.ts +7 -94
- package/tests/history-tracker.spec.ts +349 -617
- package/tests/id.test.ts +41 -44
- package/tests/load-test/data.json +346041 -0
- package/tests/load-test/entities.ts +97 -0
- package/tests/load-test/generate-data.ts +81 -0
- package/tests/load-test/lead-to-domain.mapper.ts +24 -0
- package/tests/load-test/load.test.ts +38 -0
- package/tests/repository.test.ts +30 -54
- package/tests/to-json.test.ts +14 -18
- package/tests/utils.ts +138 -102
- package/tests/value-objects.test.ts +57 -29
- package/dist/deep-proxy.d.ts +0 -36
- package/dist/deep-proxy.d.ts.map +0 -1
- package/dist/deep-proxy.js +0 -384
- package/dist/deep-proxy.js.map +0 -1
- package/src/deep-proxy.ts +0 -447
- package/tests/entity.test.ts +0 -33
package/src/id.ts
CHANGED
|
@@ -2,42 +2,33 @@
|
|
|
2
2
|
// Id Class - Smart Identity Management
|
|
3
3
|
// ============================================================================
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
// If we are in the browser, use the browser's crypto API
|
|
7
|
-
// @ts-expect-error - window.crypto is not defined in the browser
|
|
8
|
-
if (typeof window !== "undefined" && window.crypto) {
|
|
9
|
-
// @ts-expect-error - window.crypto is not defined in the browser
|
|
10
|
-
return window.crypto.randomUUID();
|
|
11
|
-
}
|
|
12
|
-
// If we are in the server, use the crypto library
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
14
|
-
const crypto = require("crypto");
|
|
15
|
-
|
|
16
|
-
return crypto.randomUUID();
|
|
17
|
-
}
|
|
5
|
+
import UUID from "./crypto";
|
|
18
6
|
|
|
19
7
|
export class Id {
|
|
20
8
|
private readonly _value: string;
|
|
21
9
|
private readonly _isNew: boolean;
|
|
22
10
|
|
|
11
|
+
|
|
23
12
|
/**
|
|
24
13
|
* Create a new Id
|
|
25
14
|
* @param value - Optional existing ID value. If not provided, generates a new UUID.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
constructor(value?:
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // New entity (generates UUID)
|
|
18
|
+
* const newId = new Id();
|
|
19
|
+
* newuser.isNew() // true
|
|
20
|
+
*
|
|
21
|
+
* // Existing entity (uses provided ID)
|
|
22
|
+
* const existingId = new Id("550e8400-e29b-41d4-a716-446655440000");
|
|
23
|
+
* existinguser.isNew() // false
|
|
24
|
+
*/
|
|
25
|
+
constructor(value: string, isNew?: boolean);
|
|
26
|
+
constructor(value?: string);
|
|
27
|
+
constructor(value?: string, isNew?: boolean) {
|
|
37
28
|
if (value !== undefined) {
|
|
38
29
|
// ID was provided - this is an existing entity
|
|
39
30
|
this._value = value;
|
|
40
|
-
this._isNew = false;
|
|
31
|
+
this._isNew = isNew ?? false;
|
|
41
32
|
} else {
|
|
42
33
|
// No ID provided - generate new one, this is a new entity
|
|
43
34
|
this._value = this.generateUUID();
|
|
@@ -88,7 +79,7 @@ export class Id {
|
|
|
88
79
|
*/
|
|
89
80
|
private generateUUID(): string {
|
|
90
81
|
// Simple UUID v4 implementation
|
|
91
|
-
return
|
|
82
|
+
return UUID();
|
|
92
83
|
}
|
|
93
84
|
|
|
94
85
|
/**
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { Id } from "./id";
|
|
|
7
7
|
export { Entity, Aggregate } from "./entity";
|
|
8
8
|
export { ValueObject } from "./value-object";
|
|
9
9
|
export { Mapper } from "./mapper";
|
|
10
|
+
export { EntitySchemaRegistry } from "./entity-schema-registry";
|
|
10
11
|
|
|
11
12
|
export * from "./validation-error";
|
|
12
13
|
|
|
@@ -46,6 +47,11 @@ export {
|
|
|
46
47
|
Search,
|
|
47
48
|
FilterValueFor,
|
|
48
49
|
PathValue,
|
|
50
|
+
OperatorsForType,
|
|
51
|
+
DateOperators,
|
|
52
|
+
NumberOperators,
|
|
53
|
+
StringOperators,
|
|
54
|
+
BooleanOperators,
|
|
55
|
+
ArrayOperators,
|
|
56
|
+
CriteriaOptions,
|
|
49
57
|
} from "./types";
|
|
50
|
-
|
|
51
|
-
// Internal (for advanced usage)
|
package/src/mapper.ts
CHANGED
package/src/paginated-result.ts
CHANGED
|
@@ -64,12 +64,6 @@ export class PaginatedResult<T> {
|
|
|
64
64
|
*/
|
|
65
65
|
static fromArray<T>(items: T[], criteria: Criteria<T>): PaginatedResult<T> {
|
|
66
66
|
let result = [...items];
|
|
67
|
-
|
|
68
|
-
// Apply filters
|
|
69
|
-
for (const filter of criteria.getFilters()) {
|
|
70
|
-
result = result.filter((item) => applyFilter(item, filter));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
67
|
let total = result.length;
|
|
74
68
|
|
|
75
69
|
const search = criteria.getSearch();
|
|
@@ -78,10 +72,15 @@ export class PaginatedResult<T> {
|
|
|
78
72
|
return search.fields.some((field) => {
|
|
79
73
|
return String(getNestedValue(item, field))
|
|
80
74
|
.toLowerCase()
|
|
81
|
-
.includes(search.value.toLowerCase());
|
|
75
|
+
.includes(search.value.trim().toLowerCase());
|
|
82
76
|
});
|
|
83
77
|
});
|
|
78
|
+
total = result.length;
|
|
79
|
+
}
|
|
84
80
|
|
|
81
|
+
// Apply filters
|
|
82
|
+
for (const filter of criteria.getFilters()) {
|
|
83
|
+
result = result.filter((item) => applyFilter(item, filter));
|
|
85
84
|
total = result.length;
|
|
86
85
|
}
|
|
87
86
|
|
|
@@ -101,7 +100,7 @@ export class PaginatedResult<T> {
|
|
|
101
100
|
|
|
102
101
|
// Apply pagination
|
|
103
102
|
const pagination = criteria.getPagination();
|
|
104
|
-
if (pagination) {
|
|
103
|
+
if (pagination && !criteria.hasSearch()) {
|
|
105
104
|
result = result.slice(
|
|
106
105
|
pagination.offset,
|
|
107
106
|
pagination.offset + pagination.limit
|
|
@@ -1,57 +1,26 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Base Repository - Abstract implementation with common logic
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
1
|
import type { Aggregate } from "../entity";
|
|
6
2
|
import type { Criteria } from "../criteria";
|
|
7
3
|
import { PaginatedResult } from "../paginated-result";
|
|
8
4
|
import { Mapper } from "../mapper";
|
|
9
5
|
|
|
10
|
-
/**
|
|
11
|
-
* Abstract base repository
|
|
12
|
-
* Implements common logic, delegates persistence to subclasses
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* class UserRepository extends BaseRepository<User, PrismaUser> {
|
|
17
|
-
* constructor(prisma: PrismaClient) {
|
|
18
|
-
* super(new UserMapper());
|
|
19
|
-
* this.prisma = prisma;
|
|
20
|
-
* }
|
|
21
|
-
*
|
|
22
|
-
* protected async insertOne(data: PrismaUser): Promise<PrismaUser> {
|
|
23
|
-
* return this.prisma.user.create({ data });
|
|
24
|
-
* }
|
|
25
|
-
*
|
|
26
|
-
* protected async updateOne(id: string, data: PrismaUser): Promise<PrismaUser> {
|
|
27
|
-
* return this.prisma.user.update({ where: { id }, data });
|
|
28
|
-
* }
|
|
29
|
-
*
|
|
30
|
-
* // ... implement other abstract methods
|
|
31
|
-
* }
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
6
|
export abstract class ReadRepository<Agg extends Aggregate<any>> {
|
|
36
|
-
abstract find(criteria
|
|
7
|
+
abstract find(criteria?: Criteria<Agg>): Promise<PaginatedResult<Agg>>;
|
|
37
8
|
abstract findById(id: string): Promise<Agg | null>;
|
|
38
|
-
abstract count(criteria
|
|
9
|
+
abstract count(criteria?: Criteria<Agg>): Promise<number>;
|
|
39
10
|
abstract exists(id: string): Promise<boolean>;
|
|
40
11
|
}
|
|
41
12
|
|
|
42
13
|
export abstract class WriteRepository<Agg extends Aggregate<any>> {
|
|
43
|
-
abstract
|
|
44
|
-
abstract update(entity: Agg): Promise<void>;
|
|
14
|
+
abstract save(entity: Agg): Promise<void>;
|
|
45
15
|
abstract delete(entity: Agg): Promise<void>;
|
|
46
16
|
}
|
|
47
17
|
|
|
48
18
|
export abstract class WriteAndRead<Agg extends Aggregate<any>> {
|
|
49
|
-
abstract find(criteria
|
|
19
|
+
abstract find(criteria?: Criteria<Agg>): Promise<PaginatedResult<Agg>>;
|
|
50
20
|
abstract findById(id: string): Promise<Agg | null>;
|
|
51
|
-
abstract
|
|
52
|
-
abstract update(entity: Agg): Promise<void>;
|
|
21
|
+
abstract save(entity: Agg): Promise<void>;
|
|
53
22
|
abstract delete(entity: Agg): Promise<void>;
|
|
54
|
-
abstract count(criteria
|
|
23
|
+
abstract count(criteria?: Criteria<Agg>): Promise<number>;
|
|
55
24
|
abstract exists(id: string): Promise<boolean>;
|
|
56
25
|
}
|
|
57
26
|
|
|
@@ -7,31 +7,6 @@ import type { IUnitOfWork, TransactionContext } from "../types";
|
|
|
7
7
|
/**
|
|
8
8
|
* Abstract Unit of Work
|
|
9
9
|
* Provides transaction management across multiple repositories
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* // Using transaction helper (recommended)
|
|
14
|
-
* await uow.transaction(async (ctx) => {
|
|
15
|
-
* const userRepo = uow.getRepository(UserRepository);
|
|
16
|
-
* const orderRepo = uow.getRepository(OrderRepository);
|
|
17
|
-
*
|
|
18
|
-
* await userRepo.save(user);
|
|
19
|
-
* await orderRepo.save(order);
|
|
20
|
-
*
|
|
21
|
-
* // Auto-commits on success, rolls back on error
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* // Manual control
|
|
25
|
-
* const ctx = await uow.begin();
|
|
26
|
-
* try {
|
|
27
|
-
* await userRepo.save(user);
|
|
28
|
-
* await orderRepo.save(order);
|
|
29
|
-
* await ctx.commit();
|
|
30
|
-
* } catch (error) {
|
|
31
|
-
* await ctx.rollback();
|
|
32
|
-
* throw error;
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
10
|
*/
|
|
36
11
|
export abstract class UnitOfWork implements IUnitOfWork {
|
|
37
12
|
protected currentContext: TransactionContext | null = null;
|
|
@@ -0,0 +1,221 @@
|
|
|
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
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create operation.
|
|
16
|
+
*/
|
|
17
|
+
export interface CreateOperation<T = any> extends BaseOperation {
|
|
18
|
+
type: "create";
|
|
19
|
+
/** Entity data to be created */
|
|
20
|
+
data: T;
|
|
21
|
+
/** Parent ID (for FK) */
|
|
22
|
+
parentId?: string;
|
|
23
|
+
/** Parent entity name */
|
|
24
|
+
parentEntity?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Update operation.
|
|
29
|
+
*/
|
|
30
|
+
export interface UpdateOperation<T = any> extends BaseOperation {
|
|
31
|
+
type: "update";
|
|
32
|
+
/** Entity ID */
|
|
33
|
+
id: string;
|
|
34
|
+
/** Current entity instance */
|
|
35
|
+
data: T;
|
|
36
|
+
/** Only fields that have changed */
|
|
37
|
+
changedFields: Record<string, any>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Delete operation.
|
|
42
|
+
*/
|
|
43
|
+
export interface DeleteOperation<T = any> extends BaseOperation {
|
|
44
|
+
type: "delete";
|
|
45
|
+
/** Entity ID to be deleted */
|
|
46
|
+
id: string;
|
|
47
|
+
/** Entity data (for reference) */
|
|
48
|
+
data: T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Union of all possible operations.
|
|
53
|
+
*/
|
|
54
|
+
export type Operation<T = any> =
|
|
55
|
+
| CreateOperation<T>
|
|
56
|
+
| UpdateOperation<T>
|
|
57
|
+
| DeleteOperation<T>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Item for batch creation.
|
|
61
|
+
*/
|
|
62
|
+
export interface BatchCreateItem<T = any> {
|
|
63
|
+
/** Entity data */
|
|
64
|
+
data: T;
|
|
65
|
+
/** Parent ID (for FK) */
|
|
66
|
+
parentId?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Item for batch update.
|
|
71
|
+
*/
|
|
72
|
+
export interface BatchUpdateItem {
|
|
73
|
+
/** Entity ID */
|
|
74
|
+
id: string;
|
|
75
|
+
/** Fields that have changed */
|
|
76
|
+
changedFields: Record<string, any>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Grouped and ordered operations for batch execution.
|
|
81
|
+
*/
|
|
82
|
+
export interface BatchOperations {
|
|
83
|
+
/**
|
|
84
|
+
* Deletes grouped by entity, ordered by depth descending (leaf → root).
|
|
85
|
+
*/
|
|
86
|
+
deletes: Array<{
|
|
87
|
+
entity: string;
|
|
88
|
+
depth: number;
|
|
89
|
+
ids: string[];
|
|
90
|
+
}>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates grouped by entity, ordered by depth ascending (root → leaf).
|
|
94
|
+
*/
|
|
95
|
+
creates: Array<{
|
|
96
|
+
entity: string;
|
|
97
|
+
depth: number;
|
|
98
|
+
items: BatchCreateItem[];
|
|
99
|
+
}>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Updates grouped by entity.
|
|
103
|
+
*/
|
|
104
|
+
updates: Array<{
|
|
105
|
+
entity: string;
|
|
106
|
+
items: BatchUpdateItem[];
|
|
107
|
+
}>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Changes detected in a collection (1:N).
|
|
112
|
+
*/
|
|
113
|
+
export interface CollectionChanges<T = any> {
|
|
114
|
+
/** Created items */
|
|
115
|
+
created: T[];
|
|
116
|
+
/** Updated items with their changes */
|
|
117
|
+
updated: Array<{
|
|
118
|
+
entity: T;
|
|
119
|
+
changes: Record<string, { from: any; to: any }>;
|
|
120
|
+
}>;
|
|
121
|
+
/** Deleted items */
|
|
122
|
+
deleted: T[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Possible states for a 1:1 relationship.
|
|
127
|
+
*/
|
|
128
|
+
export type EntityChangeState =
|
|
129
|
+
| "created" // null → Entity
|
|
130
|
+
| "updated" // Entity(id:1) → Entity(id:1) with changes
|
|
131
|
+
| "deleted" // Entity → null
|
|
132
|
+
| "replaced" // Entity(id:1) → Entity(id:2)
|
|
133
|
+
| "unchanged"; // No changes
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Change in a 1:1 entity relationship.
|
|
137
|
+
*/
|
|
138
|
+
export interface EntityChange<T = any> {
|
|
139
|
+
/** State of the change */
|
|
140
|
+
state: EntityChangeState;
|
|
141
|
+
/** Current entity (null if deleted) */
|
|
142
|
+
current: T | null;
|
|
143
|
+
/** Previous entity (null if created) */
|
|
144
|
+
previous: T | null;
|
|
145
|
+
/** Field changes (if state === 'updated') */
|
|
146
|
+
changes?: Record<string, { from: any; to: any }>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Change in a primitive field.
|
|
151
|
+
*/
|
|
152
|
+
export interface FieldChange<T = any> {
|
|
153
|
+
from: T;
|
|
154
|
+
to: T;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Extracts the props type from an Entity or ValueObject.
|
|
159
|
+
*/
|
|
160
|
+
export type ExtractProps<T> = T extends Entity<infer P>
|
|
161
|
+
? P
|
|
162
|
+
: T extends ValueObject<infer P>
|
|
163
|
+
? P
|
|
164
|
+
: never;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Keys of primitive properties (not Entity, ValueObject or Array).
|
|
168
|
+
*/
|
|
169
|
+
export type PrimitiveKeys<T> = {
|
|
170
|
+
[K in keyof T]: T[K] extends
|
|
171
|
+
| Entity<any>
|
|
172
|
+
| ValueObject<any>
|
|
173
|
+
| Array<any>
|
|
174
|
+
| undefined
|
|
175
|
+
? never
|
|
176
|
+
: K;
|
|
177
|
+
}[keyof T];
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Keys of collections (arrays).
|
|
181
|
+
*/
|
|
182
|
+
export type CollectionKeys<T> = {
|
|
183
|
+
[K in keyof T]: T[K] extends Array<any> ? K : never;
|
|
184
|
+
}[keyof T];
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Keys of single entities (1:1).
|
|
188
|
+
*/
|
|
189
|
+
export type SingleEntityKeys<T> = {
|
|
190
|
+
[K in keyof T]: T[K] extends Entity<any> | ValueObject<any> | null | undefined
|
|
191
|
+
? T[K] extends Array<any>
|
|
192
|
+
? never
|
|
193
|
+
: K
|
|
194
|
+
: never;
|
|
195
|
+
}[keyof T];
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Change history entry.
|
|
199
|
+
*/
|
|
200
|
+
export interface HistoryEntry {
|
|
201
|
+
path: string;
|
|
202
|
+
previousValue: any;
|
|
203
|
+
currentValue: any;
|
|
204
|
+
timestamp: number;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Metadata from a tracked entity/VO.
|
|
209
|
+
*/
|
|
210
|
+
export interface TrackedEntityMetadata {
|
|
211
|
+
/** Entity name */
|
|
212
|
+
entityName: string;
|
|
213
|
+
/** Depth in the tree */
|
|
214
|
+
depth: number;
|
|
215
|
+
/** Parent ID */
|
|
216
|
+
parentId?: string;
|
|
217
|
+
/** Parent entity name */
|
|
218
|
+
parentEntity?: string;
|
|
219
|
+
/** Path in the object (e.g., 'posts[0].comments[1]') */
|
|
220
|
+
path: string;
|
|
221
|
+
}
|
package/src/types/criteria.ts
CHANGED
|
@@ -17,12 +17,65 @@ export const FILTER_OPERATORS = [
|
|
|
17
17
|
"isNotNull",
|
|
18
18
|
] as const;
|
|
19
19
|
|
|
20
|
+
export type FilterOperator = (typeof FILTER_OPERATORS)[number];
|
|
21
|
+
|
|
22
|
+
export type StringOperators =
|
|
23
|
+
| "equals"
|
|
24
|
+
| "notEquals"
|
|
25
|
+
| "contains"
|
|
26
|
+
| "startsWith"
|
|
27
|
+
| "endsWith"
|
|
28
|
+
| "in"
|
|
29
|
+
| "notIn"
|
|
30
|
+
| "isNull"
|
|
31
|
+
| "isNotNull";
|
|
32
|
+
|
|
33
|
+
export type NumberOperators =
|
|
34
|
+
| "equals"
|
|
35
|
+
| "notEquals"
|
|
36
|
+
| "greaterThan"
|
|
37
|
+
| "greaterThanOrEqual"
|
|
38
|
+
| "lessThan"
|
|
39
|
+
| "lessThanOrEqual"
|
|
40
|
+
| "in"
|
|
41
|
+
| "notIn"
|
|
42
|
+
| "between"
|
|
43
|
+
| "isNull"
|
|
44
|
+
| "isNotNull";
|
|
45
|
+
|
|
46
|
+
export type DateOperators =
|
|
47
|
+
| "equals"
|
|
48
|
+
| "notEquals"
|
|
49
|
+
| "greaterThan"
|
|
50
|
+
| "greaterThanOrEqual"
|
|
51
|
+
| "lessThan"
|
|
52
|
+
| "lessThanOrEqual"
|
|
53
|
+
| "in"
|
|
54
|
+
| "notIn"
|
|
55
|
+
| "between"
|
|
56
|
+
| "isNull"
|
|
57
|
+
| "isNotNull";
|
|
58
|
+
|
|
59
|
+
export type BooleanOperators = "equals" | "notEquals" | "isNull" | "isNotNull";
|
|
60
|
+
|
|
61
|
+
export type ArrayOperators = "in" | "notIn" | "isNull" | "isNotNull";
|
|
62
|
+
|
|
63
|
+
export type OperatorsForType<T> = T extends string
|
|
64
|
+
? StringOperators
|
|
65
|
+
: T extends number
|
|
66
|
+
? NumberOperators
|
|
67
|
+
: T extends Date
|
|
68
|
+
? DateOperators
|
|
69
|
+
: T extends boolean
|
|
70
|
+
? BooleanOperators
|
|
71
|
+
: T extends Array<any>
|
|
72
|
+
? ArrayOperators
|
|
73
|
+
: FilterOperator;
|
|
74
|
+
|
|
20
75
|
export type FilterValueFor<T> =
|
|
21
|
-
| T
|
|
22
|
-
| (T extends number | Date
|
|
23
|
-
|
|
24
|
-
: never)
|
|
25
|
-
| T[] // in, notIn
|
|
76
|
+
| T
|
|
77
|
+
| (T extends number | Date ? [T, T] : never)
|
|
78
|
+
| T[]
|
|
26
79
|
| null;
|
|
27
80
|
|
|
28
81
|
export type PathValue<
|
|
@@ -30,24 +83,34 @@ export type PathValue<
|
|
|
30
83
|
P extends string
|
|
31
84
|
> = P extends `${infer K}.${infer Rest}`
|
|
32
85
|
? K extends keyof T
|
|
33
|
-
?
|
|
86
|
+
? T[K] extends Array<infer U>
|
|
87
|
+
? PathValue<U, Rest>
|
|
88
|
+
: PathValue<T[K], Rest>
|
|
34
89
|
: never
|
|
35
90
|
: P extends keyof T
|
|
36
91
|
? T[P]
|
|
37
92
|
: never;
|
|
38
93
|
|
|
39
|
-
export type FilterOperator = (typeof FILTER_OPERATORS)[number];
|
|
40
|
-
|
|
41
94
|
export interface Filter<TField = string, TValue = unknown> {
|
|
42
95
|
field: TField;
|
|
43
|
-
operator: FilterOperator
|
|
96
|
+
operator: unknown extends TValue ? FilterOperator : OperatorsForType<TValue>;
|
|
44
97
|
value: TValue;
|
|
98
|
+
options?: CriteriaOptions;
|
|
45
99
|
}
|
|
46
100
|
|
|
47
101
|
export type TypedFilter<T> = {
|
|
48
|
-
[K in FieldPath<T>]:
|
|
102
|
+
[K in FieldPath<T>]: {
|
|
103
|
+
field: K;
|
|
104
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>;
|
|
105
|
+
value: FilterValueFor<NonNullable<PathValue<T, K>>>;
|
|
106
|
+
options?: CriteriaOptions;
|
|
107
|
+
};
|
|
49
108
|
}[FieldPath<T>];
|
|
50
109
|
|
|
110
|
+
export type CriteriaAdapter<Input, Output> = {
|
|
111
|
+
[K in FieldPath<Input>]?: FieldPath<Output>;
|
|
112
|
+
};
|
|
113
|
+
|
|
51
114
|
export type OrderDirection = "asc" | "desc";
|
|
52
115
|
|
|
53
116
|
export interface Order {
|
|
@@ -55,6 +118,11 @@ export interface Order {
|
|
|
55
118
|
direction: OrderDirection;
|
|
56
119
|
}
|
|
57
120
|
|
|
121
|
+
export type TypedOrder<T> = {
|
|
122
|
+
field: FieldPath<T>;
|
|
123
|
+
direction: OrderDirection;
|
|
124
|
+
};
|
|
125
|
+
|
|
58
126
|
export interface Pagination {
|
|
59
127
|
page: number;
|
|
60
128
|
limit: number;
|
|
@@ -75,10 +143,20 @@ export interface PaginationMeta {
|
|
|
75
143
|
hasPrevious: boolean;
|
|
76
144
|
}
|
|
77
145
|
|
|
78
|
-
export
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
146
|
+
export interface CriteriaOptions {
|
|
147
|
+
quantifier?: "some" | "every" | "none";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type ExcludeBuiltInKeys<T> = Exclude<keyof T, keyof any[] | number | symbol>;
|
|
151
|
+
|
|
152
|
+
export type FieldPath<T> = T extends Primitive
|
|
153
|
+
? never
|
|
154
|
+
: {
|
|
155
|
+
[K in ExcludeBuiltInKeys<T> & string]: NonNullable<T[K]> extends Primitive
|
|
156
|
+
? K
|
|
157
|
+
: NonNullable<T[K]> extends Array<infer U>
|
|
158
|
+
? U extends Primitive
|
|
159
|
+
? K
|
|
160
|
+
: K | `${K}.${FieldPath<U>}`
|
|
161
|
+
: `${K}.${FieldPath<NonNullable<T[K]>>}`;
|
|
162
|
+
}[ExcludeBuiltInKeys<T> & string];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TrackedEntityMetadata } from "./change-tracker";
|
|
1
2
|
import { BaseProps } from "./domain";
|
|
2
3
|
import { IsArray, NonUndefined, UnwrapArray } from "./utils";
|
|
3
4
|
|
|
@@ -7,6 +8,18 @@ export interface ChangeEvent<T> {
|
|
|
7
8
|
path: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
export interface TrackedItem {
|
|
12
|
+
entity: any;
|
|
13
|
+
metadata: TrackedEntityMetadata;
|
|
14
|
+
originalState: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ArrayState {
|
|
18
|
+
cloned: any[];
|
|
19
|
+
original: any[];
|
|
20
|
+
metadata: TrackedEntityMetadata;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
export interface ArrayChangeEvent<T> {
|
|
11
24
|
toCreate: T[];
|
|
12
25
|
toUpdate: T[];
|
package/src/types/utils.ts
CHANGED
|
@@ -14,16 +14,7 @@ export type DeepJsonResult<T> = {
|
|
|
14
14
|
: T[K];
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export type DeepKeyOf<T, K extends keyof T = keyof T> = K extends string
|
|
18
|
-
? T[K] extends Primitive
|
|
19
|
-
? K
|
|
20
|
-
: T[K] extends object
|
|
21
|
-
? `${K}` | `${K}.${DeepKeyOf<T[K]>}`
|
|
22
|
-
: never
|
|
23
|
-
: never;
|
|
24
|
-
|
|
25
17
|
export type Primitive = string | number | boolean | Date | null | undefined;
|
|
26
|
-
|
|
27
18
|
export type UnwrapArray<T> = T extends Array<infer U> ? U : never;
|
|
28
19
|
export type IsArray<T> = T extends Array<any> ? true : false;
|
|
29
20
|
export type NonUndefined<T> = T extends undefined ? never : T;
|