@woltz/rich-domain 1.3.0 → 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.
Files changed (76) hide show
  1. package/dist/aggregate-changes.js +1 -1
  2. package/dist/aggregate-changes.js.map +1 -1
  3. package/dist/base-entity.d.ts.map +1 -1
  4. package/dist/base-entity.js +17 -5
  5. package/dist/base-entity.js.map +1 -1
  6. package/dist/change-tracker.d.ts +1 -1
  7. package/dist/change-tracker.d.ts.map +1 -1
  8. package/dist/change-tracker.js +20 -8
  9. package/dist/change-tracker.js.map +1 -1
  10. package/dist/criteria.js +6 -5
  11. package/dist/criteria.js.map +1 -1
  12. package/dist/domain-event-bus.js +4 -4
  13. package/dist/domain-event-bus.js.map +1 -1
  14. package/dist/domain-event.js +3 -0
  15. package/dist/domain-event.js.map +1 -1
  16. package/dist/entity-changes.js +1 -0
  17. package/dist/entity-changes.js.map +1 -1
  18. package/dist/entity-schema-registry.d.ts +4 -0
  19. package/dist/entity-schema-registry.d.ts.map +1 -1
  20. package/dist/entity-schema-registry.js +8 -6
  21. package/dist/entity-schema-registry.js.map +1 -1
  22. package/dist/exceptions.js +26 -1
  23. package/dist/exceptions.js.map +1 -1
  24. package/dist/id.js +2 -0
  25. package/dist/id.js.map +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/paginated-result.d.ts.map +1 -1
  30. package/dist/paginated-result.js +9 -0
  31. package/dist/paginated-result.js.map +1 -1
  32. package/dist/repository/unit-of-work.js +3 -7
  33. package/dist/repository/unit-of-work.js.map +1 -1
  34. package/dist/types/domain.d.ts +0 -1
  35. package/dist/types/domain.d.ts.map +1 -1
  36. package/dist/validation-error.d.ts +15 -1
  37. package/dist/validation-error.d.ts.map +1 -1
  38. package/dist/validation-error.js +46 -3
  39. package/dist/validation-error.js.map +1 -1
  40. package/dist/value-object.d.ts.map +1 -1
  41. package/dist/value-object.js +29 -1
  42. package/dist/value-object.js.map +1 -1
  43. package/package.json +9 -11
  44. package/eslint.config.js +0 -57
  45. package/jest.config.js +0 -21
  46. package/src/aggregate-changes.ts +0 -444
  47. package/src/base-entity.ts +0 -404
  48. package/src/change-tracker.ts +0 -1133
  49. package/src/constants.ts +0 -81
  50. package/src/criteria.ts +0 -521
  51. package/src/crypto.ts +0 -31
  52. package/src/domain-event-bus.ts +0 -152
  53. package/src/domain-event.ts +0 -49
  54. package/src/entity-changes.ts +0 -146
  55. package/src/entity-schema-registry.ts +0 -502
  56. package/src/entity.ts +0 -5
  57. package/src/exceptions.ts +0 -435
  58. package/src/id.ts +0 -98
  59. package/src/index.ts +0 -52
  60. package/src/mapper.ts +0 -6
  61. package/src/paginated-result.ts +0 -238
  62. package/src/repository/base-repository.ts +0 -33
  63. package/src/repository/index.ts +0 -3
  64. package/src/repository/unit-of-work.ts +0 -76
  65. package/src/types/change-tracker.ts +0 -264
  66. package/src/types/criteria.ts +0 -159
  67. package/src/types/domain-event.ts +0 -38
  68. package/src/types/domain.ts +0 -34
  69. package/src/types/index.ts +0 -7
  70. package/src/types/standard-schema.ts +0 -19
  71. package/src/types/unit-of-work.ts +0 -46
  72. package/src/types/utils.ts +0 -20
  73. package/src/utils/criteria-operator-validation.ts +0 -209
  74. package/src/utils/helpers.ts +0 -34
  75. package/src/validation-error.ts +0 -91
  76. package/src/value-object.ts +0 -244
@@ -1,238 +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
- return PaginatedResult.create(result, pagination, total);
85
- }
86
-
87
- /**
88
- * Converts the result to JSON, deeply serializing all entities/aggregates/value objects
89
- * - Entities/Aggregates → calls toJSON() recursively
90
- * - Value Objects → calls toJSON()
91
- * - Id → converts to string
92
- * - Arrays → maps recursively
93
- * - Plain objects → serializes properties recursively
94
- * - Primitives → returns as-is
95
- */
96
- toJSON(): PaginatedJsonResult<T> {
97
- return {
98
- data: this.data.map((item) =>
99
- this.deepSerialize(item)
100
- ) as InferJsonResult<T>[],
101
- meta: this.meta,
102
- };
103
- }
104
-
105
- /**
106
- * Deep serialization logic (similar to BaseEntity.deepToJson)
107
- */
108
- private deepSerialize(obj: any): any {
109
- if (obj === null || obj === undefined) return obj;
110
-
111
- if (obj instanceof Id) return obj.value;
112
-
113
- if (Array.isArray(obj)) {
114
- return obj.map((item) => this.deepSerialize(item));
115
- }
116
-
117
- if (obj && typeof obj.toJSON === "function") {
118
- return obj.toJSON();
119
- }
120
-
121
- if (typeof obj === "object") {
122
- const result: any = {};
123
- for (const key in obj) {
124
- if (obj.hasOwnProperty(key)) {
125
- result[key] = this.deepSerialize(obj[key]);
126
- }
127
- }
128
- return result;
129
- }
130
-
131
- return obj;
132
- }
133
-
134
- /**
135
- * Transform each item in the result using a mapper function
136
- */
137
- map<U>(fn: (item: T) => U): PaginatedResult<U> {
138
- return new PaginatedResult(this.data.map(fn), this.meta);
139
- }
140
-
141
- /**
142
- * Check if result has no data
143
- */
144
- get isEmpty(): boolean {
145
- return this.data.length === 0;
146
- }
147
-
148
- /**
149
- * Check if there are more pages available
150
- */
151
- get hasMore(): boolean {
152
- return this.meta.hasNext;
153
- }
154
- }
155
-
156
- function applyFilter<T>(item: T, filter: Filter): boolean {
157
- const value = getNestedValue(item, filter.field);
158
-
159
- const isValueDate = value instanceof Date;
160
-
161
- const parseValue = (v: any) => {
162
- if (isValueDate && typeof v === "string") return new Date(v);
163
- if (isValueDate && typeof v === "number") return new Date(v);
164
- return v;
165
- };
166
-
167
- switch (filter.operator) {
168
- case "equals":
169
- return value === filter.value;
170
-
171
- case "notEquals":
172
- return value !== filter.value;
173
-
174
- case "greaterThan": {
175
- const compareTo = parseValue(filter.value);
176
- return isValueDate ? value > compareTo : value > (filter.value as any);
177
- }
178
-
179
- case "greaterThanOrEqual": {
180
- const compareTo = parseValue(filter.value);
181
- return isValueDate ? value >= compareTo : value >= (filter.value as any);
182
- }
183
-
184
- case "lessThan":
185
- const lt = parseValue(filter.value);
186
- return isValueDate ? value < lt : value < (filter.value as any);
187
-
188
- case "lessThanOrEqual":
189
- const lte = parseValue(filter.value);
190
- return isValueDate ? value <= lte : value <= (filter.value as any);
191
-
192
- case "contains":
193
- return String(value)
194
- .toLowerCase()
195
- .includes(String(filter.value).toLowerCase());
196
-
197
- case "startsWith":
198
- return String(value)
199
- .toLowerCase()
200
- .startsWith(String(filter.value).toLowerCase());
201
-
202
- case "endsWith":
203
- return String(value)
204
- .toLowerCase()
205
- .endsWith(String(filter.value).toLowerCase());
206
-
207
- case "in":
208
- return Array.isArray(filter.value) && filter.value.includes(value);
209
-
210
- case "notIn":
211
- return Array.isArray(filter.value) && !filter.value.includes(value);
212
-
213
- case "between":
214
- if (Array.isArray(filter.value) && filter.value.length === 2) {
215
- const [min, max] = filter.value.map(parseValue);
216
- return isValueDate
217
- ? value >= min && value <= max
218
- : value >= filter.value[0] && value <= filter.value[1];
219
- }
220
- return false;
221
-
222
- case "isNull":
223
- return value === null || value === undefined;
224
-
225
- case "isNotNull":
226
- return value !== null && value !== undefined;
227
-
228
- default:
229
- return true;
230
- }
231
- }
232
-
233
- function getNestedValue(obj: any, path: string): any {
234
- return path.split(".").reduce((current, key) => {
235
- if (current === null || current === undefined) return undefined;
236
- return current[key];
237
- }, obj);
238
- }
@@ -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
- }
@@ -1,3 +0,0 @@
1
- export { Mapper } from "../mapper";
2
- export * from "./base-repository";
3
- export { UnitOfWork, BaseTransactionContext } from "./unit-of-work";
@@ -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
- }