@woltz/rich-domain 1.2.1 → 1.2.4
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 +56 -0
- package/dist/base-entity.d.ts +1 -1
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +13 -46
- package/dist/base-entity.js.map +1 -1
- package/dist/{history-tracker.d.ts → change-tracker.d.ts} +3 -3
- package/dist/change-tracker.d.ts.map +1 -0
- package/dist/{history-tracker.js → change-tracker.js} +11 -57
- package/dist/change-tracker.js.map +1 -0
- package/dist/constants.d.ts +7 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +65 -0
- package/dist/constants.js.map +1 -1
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +6 -4
- package/dist/criteria.js.map +1 -1
- package/dist/domain-event.d.ts.map +1 -1
- package/dist/domain-event.js +0 -3
- package/dist/domain-event.js.map +1 -1
- package/dist/entity-changes.d.ts.map +1 -1
- package/dist/entity-changes.js +0 -4
- package/dist/entity-changes.js.map +1 -1
- package/dist/entity-schema-registry.d.ts.map +1 -1
- package/dist/entity-schema-registry.js +2 -8
- package/dist/entity-schema-registry.js.map +1 -1
- package/dist/entity.d.ts +0 -6
- package/dist/entity.d.ts.map +1 -1
- package/dist/entity.js +0 -9
- package/dist/entity.js.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -12
- package/dist/index.js.map +1 -1
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +0 -15
- package/dist/paginated-result.js.map +1 -1
- package/dist/repository/base-repository.d.ts +1 -1
- package/dist/repository/base-repository.d.ts.map +1 -1
- package/dist/repository/index.d.ts.map +1 -1
- package/dist/repository/index.js +0 -6
- package/dist/repository/index.js.map +1 -1
- package/dist/repository/unit-of-work.d.ts.map +1 -1
- package/dist/repository/unit-of-work.js +0 -3
- package/dist/repository/unit-of-work.js.map +1 -1
- package/dist/types/change-tracker.d.ts +10 -0
- package/dist/types/change-tracker.d.ts.map +1 -1
- package/dist/types/domain.d.ts +4 -6
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/criteria-operator-validation.d.ts +1 -0
- package/dist/utils/criteria-operator-validation.d.ts.map +1 -1
- package/dist/utils/criteria-operator-validation.js +39 -17
- package/dist/utils/criteria-operator-validation.js.map +1 -1
- package/dist/validation-error.d.ts.map +1 -1
- package/dist/validation-error.js +1 -3
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.d.ts +1 -1
- package/dist/value-object.d.ts.map +1 -1
- package/dist/value-object.js +0 -1
- package/dist/value-object.js.map +1 -1
- package/package.json +1 -1
- package/src/base-entity.ts +13 -56
- package/src/{history-tracker.ts → change-tracker.ts} +23 -73
- package/src/constants.ts +75 -1
- package/src/criteria.ts +9 -3
- package/src/domain-event.ts +0 -4
- package/src/entity-changes.ts +0 -5
- package/src/entity-schema-registry.ts +2 -22
- package/src/entity.ts +0 -11
- package/src/index.ts +15 -20
- package/src/paginated-result.ts +0 -21
- package/src/repository/base-repository.ts +1 -1
- package/src/repository/index.ts +0 -9
- package/src/repository/unit-of-work.ts +0 -4
- package/src/types/change-tracker.ts +16 -4
- package/src/types/domain.ts +4 -8
- package/src/types/index.ts +1 -1
- package/src/utils/criteria-operator-validation.ts +57 -19
- package/src/validation-error.ts +1 -3
- package/src/value-object.ts +1 -2
- package/tests/depth/deep-tracking.test.ts +554 -0
- package/tests/history-tracker.spec.ts +1 -1
- package/tests/load-test/data.json +248187 -247017
- package/dist/history-tracker.d.ts.map +0 -1
- package/dist/history-tracker.js.map +0 -1
- package/dist/types/history-tracker.d.ts +0 -47
- package/dist/types/history-tracker.d.ts.map +0 -1
- package/dist/types/history-tracker.js +0 -2
- package/dist/types/history-tracker.js.map +0 -1
- package/src/types/history-tracker.ts +0 -58
|
@@ -8,17 +8,14 @@ import { Id } from "./id";
|
|
|
8
8
|
export interface EntitySchema {
|
|
9
9
|
/** Entity name in the domain (e.g., 'User', 'Post') */
|
|
10
10
|
entity: string;
|
|
11
|
-
|
|
12
11
|
/** Table name in the database (e.g., 'users', 'blog_posts') */
|
|
13
12
|
table: string;
|
|
14
|
-
|
|
15
13
|
/**
|
|
16
14
|
* Field mapping: domain → database.
|
|
17
15
|
* Only include fields with different names.
|
|
18
16
|
* @example { email: 'user_email', createdAt: 'created_at' }
|
|
19
17
|
*/
|
|
20
18
|
fields?: Record<string, string>;
|
|
21
|
-
|
|
22
19
|
/**
|
|
23
20
|
* FK configuration for parent relation.
|
|
24
21
|
*/
|
|
@@ -148,15 +145,11 @@ export class EntitySchemaRegistry {
|
|
|
148
145
|
mapFields(entity: string, data: Record<string, any>): MappedEntityData {
|
|
149
146
|
const fields = this.getFieldsMap(entity);
|
|
150
147
|
const result: MappedEntityData = {};
|
|
151
|
-
|
|
152
148
|
for (const [key, value] of Object.entries(data)) {
|
|
153
|
-
if (this.isEntityOrCollection(value))
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
149
|
+
if (this.isEntityOrCollection(value)) continue;
|
|
156
150
|
const mappedKey = fields[key] ?? key;
|
|
157
151
|
result[mappedKey] = this.normalizeValue(value);
|
|
158
152
|
}
|
|
159
|
-
|
|
160
153
|
return result;
|
|
161
154
|
}
|
|
162
155
|
|
|
@@ -173,26 +166,18 @@ export class EntitySchemaRegistry {
|
|
|
173
166
|
): MappedEntityData {
|
|
174
167
|
const fields = this.getFieldsMap(entity);
|
|
175
168
|
const result: MappedEntityData = {};
|
|
176
|
-
|
|
177
|
-
// Map ID if it is an Entity or has entity-like structure
|
|
178
169
|
const hasId = (domainEntity as any).id;
|
|
179
170
|
if (hasId) {
|
|
180
|
-
// Extract id value
|
|
181
171
|
const idValue = hasId.value ?? hasId;
|
|
182
172
|
result["id"] = idValue;
|
|
183
173
|
}
|
|
184
|
-
|
|
185
|
-
// Get props
|
|
186
174
|
const props = (domainEntity as any).props || domainEntity;
|
|
187
|
-
|
|
188
175
|
for (const [key, value] of Object.entries(props)) {
|
|
189
|
-
if (key === "id") continue;
|
|
176
|
+
if (key === "id") continue;
|
|
190
177
|
if (this.isEntityOrCollection(value)) continue;
|
|
191
|
-
|
|
192
178
|
const mappedKey = fields[key] ?? key;
|
|
193
179
|
result[mappedKey] = this.normalizeValue(value);
|
|
194
180
|
}
|
|
195
|
-
|
|
196
181
|
return result;
|
|
197
182
|
}
|
|
198
183
|
|
|
@@ -206,7 +191,6 @@ export class EntitySchemaRegistry {
|
|
|
206
191
|
getParentFk(entity: string, parentId: string): Record<string, string> | null {
|
|
207
192
|
const schema = this.getSchema(entity);
|
|
208
193
|
if (!schema.parentFk) return null;
|
|
209
|
-
|
|
210
194
|
return { [schema.parentFk.field]: parentId };
|
|
211
195
|
}
|
|
212
196
|
|
|
@@ -250,12 +234,9 @@ export class EntitySchemaRegistry {
|
|
|
250
234
|
if (Array.isArray(value)) return true;
|
|
251
235
|
if (value instanceof Entity) return true;
|
|
252
236
|
if (value instanceof ValueObject) return true;
|
|
253
|
-
|
|
254
|
-
// Checks if it has the structure of an Entity (object with 'id' that has 'value')
|
|
255
237
|
if (typeof value === 'object' && value.id && typeof value.id === 'object' && 'value' in value.id) {
|
|
256
238
|
return true;
|
|
257
239
|
}
|
|
258
|
-
|
|
259
240
|
return false;
|
|
260
241
|
}
|
|
261
242
|
|
|
@@ -267,7 +248,6 @@ export class EntitySchemaRegistry {
|
|
|
267
248
|
if (value instanceof Id) return value.value;
|
|
268
249
|
if (value instanceof Date) return value;
|
|
269
250
|
if (typeof value === "object" && "value" in value) {
|
|
270
|
-
// Might be an ID or other wrapper
|
|
271
251
|
return value.value;
|
|
272
252
|
}
|
|
273
253
|
return value;
|
package/src/entity.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Entity & Aggregate Classes
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
1
|
import { BaseEntity } from "./base-entity";
|
|
6
2
|
import { BaseProps } from "./types";
|
|
7
3
|
|
|
8
|
-
/**
|
|
9
|
-
* Entity - Has identity and lifecycle, but is not an aggregate root
|
|
10
|
-
*/
|
|
11
4
|
export class Entity<T extends BaseProps> extends BaseEntity<T> {}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Aggregate - Aggregate root that manages a consistency boundary
|
|
15
|
-
*/
|
|
16
5
|
export class Aggregate<T extends BaseProps> extends BaseEntity<T> {}
|
package/src/index.ts
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Rich Domain Library - Main Exports
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
// Core Classes
|
|
6
|
-
export { Id } from "./id";
|
|
7
|
-
export { Entity, Aggregate } from "./entity";
|
|
8
|
-
export { ValueObject } from "./value-object";
|
|
9
|
-
export { Mapper } from "./mapper";
|
|
10
|
-
export { EntitySchemaRegistry } from "./entity-schema-registry";
|
|
11
|
-
|
|
12
1
|
export * from "./validation-error";
|
|
13
|
-
|
|
14
|
-
// Domain Events
|
|
15
2
|
export * from "./domain-event";
|
|
16
3
|
export * from "./domain-event-bus";
|
|
17
4
|
export * from "./exceptions";
|
|
18
|
-
|
|
19
|
-
// Criteria
|
|
20
5
|
export * from "./criteria";
|
|
21
6
|
export * from "./paginated-result";
|
|
22
|
-
|
|
23
|
-
// Repository
|
|
24
7
|
export * from "./repository";
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
export { Id } from "./id";
|
|
9
|
+
export { Entity, Aggregate } from "./entity";
|
|
10
|
+
export { ValueObject } from "./value-object";
|
|
11
|
+
export { Mapper } from "./mapper";
|
|
12
|
+
export { EntitySchemaRegistry } from "./entity-schema-registry";
|
|
13
|
+
export { AggregateChanges } from "./aggregate-changes";
|
|
27
14
|
export {
|
|
28
15
|
DomainEventHandler,
|
|
29
16
|
EntityHooks,
|
|
@@ -41,7 +28,6 @@ export {
|
|
|
41
28
|
Order,
|
|
42
29
|
IUnitOfWork,
|
|
43
30
|
IDomainEventHandler,
|
|
44
|
-
EntityId,
|
|
45
31
|
FieldPath,
|
|
46
32
|
FilterOperator,
|
|
47
33
|
Search,
|
|
@@ -55,3 +41,12 @@ export {
|
|
|
55
41
|
ArrayOperators,
|
|
56
42
|
CriteriaOptions,
|
|
57
43
|
} from "./types";
|
|
44
|
+
export {
|
|
45
|
+
ARRAY_OPERATORS,
|
|
46
|
+
BOOLEAN_OPERATORS,
|
|
47
|
+
DATE_OPERATORS,
|
|
48
|
+
NUMBER_OPERATORS,
|
|
49
|
+
STRING_OPERATORS,
|
|
50
|
+
FILTER_OPERATORS,
|
|
51
|
+
} from "./constants";
|
|
52
|
+
export { isValidOperatorForType } from "./utils/criteria-operator-validation";
|
package/src/paginated-result.ts
CHANGED
|
@@ -2,10 +2,6 @@ import { Id } from "./id";
|
|
|
2
2
|
import type { Criteria } from "./criteria";
|
|
3
3
|
import type { Pagination, PaginationMeta, Filter } from "./types";
|
|
4
4
|
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// Type Utilities
|
|
7
|
-
// ============================================================================
|
|
8
|
-
|
|
9
5
|
/**
|
|
10
6
|
* Infers the JSON result type from T
|
|
11
7
|
* - If T has toJson(), returns its return type
|
|
@@ -21,10 +17,6 @@ export type PaginatedJsonResult<T> = {
|
|
|
21
17
|
meta: PaginationMeta;
|
|
22
18
|
};
|
|
23
19
|
|
|
24
|
-
// ============================================================================
|
|
25
|
-
// PaginatedResult Class
|
|
26
|
-
// ============================================================================
|
|
27
|
-
|
|
28
20
|
export class PaginatedResult<T> {
|
|
29
21
|
constructor(
|
|
30
22
|
public readonly data: T[],
|
|
@@ -78,13 +70,11 @@ export class PaginatedResult<T> {
|
|
|
78
70
|
total = result.length;
|
|
79
71
|
}
|
|
80
72
|
|
|
81
|
-
// Apply filters
|
|
82
73
|
for (const filter of criteria.getFilters()) {
|
|
83
74
|
result = result.filter((item) => applyFilter(item, filter));
|
|
84
75
|
total = result.length;
|
|
85
76
|
}
|
|
86
77
|
|
|
87
|
-
// Apply ordering
|
|
88
78
|
for (const order of criteria.getOrders().reverse()) {
|
|
89
79
|
result.sort((a, b) => {
|
|
90
80
|
const aVal = getNestedValue(a, order.field);
|
|
@@ -98,7 +88,6 @@ export class PaginatedResult<T> {
|
|
|
98
88
|
});
|
|
99
89
|
}
|
|
100
90
|
|
|
101
|
-
// Apply pagination
|
|
102
91
|
const pagination = criteria.getPagination();
|
|
103
92
|
if (pagination && !criteria.hasSearch()) {
|
|
104
93
|
result = result.slice(
|
|
@@ -108,7 +97,6 @@ export class PaginatedResult<T> {
|
|
|
108
97
|
return PaginatedResult.create(result, pagination, total);
|
|
109
98
|
}
|
|
110
99
|
|
|
111
|
-
// No pagination - return all with default meta
|
|
112
100
|
return PaginatedResult.create(
|
|
113
101
|
result,
|
|
114
102
|
{ page: 1, limit: result.length, offset: 0 },
|
|
@@ -140,20 +128,16 @@ export class PaginatedResult<T> {
|
|
|
140
128
|
private deepSerialize(obj: any): any {
|
|
141
129
|
if (obj === null || obj === undefined) return obj;
|
|
142
130
|
|
|
143
|
-
// Id → string
|
|
144
131
|
if (obj instanceof Id) return obj.value;
|
|
145
132
|
|
|
146
|
-
// Arrays → map recursively
|
|
147
133
|
if (Array.isArray(obj)) {
|
|
148
134
|
return obj.map((item) => this.deepSerialize(item));
|
|
149
135
|
}
|
|
150
136
|
|
|
151
|
-
// Objects with toJson() method (Entity/Aggregate/ValueObject)
|
|
152
137
|
if (obj && typeof obj.toJson === "function") {
|
|
153
138
|
return obj.toJson();
|
|
154
139
|
}
|
|
155
140
|
|
|
156
|
-
// Plain objects → serialize properties recursively
|
|
157
141
|
if (typeof obj === "object") {
|
|
158
142
|
const result: any = {};
|
|
159
143
|
for (const key in obj) {
|
|
@@ -164,7 +148,6 @@ export class PaginatedResult<T> {
|
|
|
164
148
|
return result;
|
|
165
149
|
}
|
|
166
150
|
|
|
167
|
-
// Primitives
|
|
168
151
|
return obj;
|
|
169
152
|
}
|
|
170
153
|
|
|
@@ -190,10 +173,6 @@ export class PaginatedResult<T> {
|
|
|
190
173
|
}
|
|
191
174
|
}
|
|
192
175
|
|
|
193
|
-
// ============================================================================
|
|
194
|
-
// Helper Functions (moved from criteria.ts)
|
|
195
|
-
// ============================================================================
|
|
196
|
-
|
|
197
176
|
function applyFilter<T>(item: T, filter: Filter): boolean {
|
|
198
177
|
const value = getNestedValue(item, filter.field);
|
|
199
178
|
|
|
@@ -29,5 +29,5 @@ export abstract class Repository<
|
|
|
29
29
|
> extends WriteAndRead<TDomain> {
|
|
30
30
|
protected abstract readonly mapperToDomain: Mapper<unknown, TDomain>;
|
|
31
31
|
protected abstract readonly mapperToPersistence: Mapper<TDomain, unknown>;
|
|
32
|
-
abstract get model(): any;
|
|
32
|
+
protected abstract get model(): any;
|
|
33
33
|
}
|
package/src/repository/index.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Repository Module - Clean exports
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
// Mapper
|
|
6
1
|
export { Mapper } from "../mapper";
|
|
7
|
-
|
|
8
|
-
// Base implementations
|
|
9
2
|
export * from "./base-repository";
|
|
10
|
-
|
|
11
|
-
// Unit of Work
|
|
12
3
|
export { UnitOfWork, BaseTransactionContext } from "./unit-of-work";
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Unit of Work - Simple transaction management
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
1
|
import type { IUnitOfWork, TransactionContext } from "../types";
|
|
6
2
|
|
|
7
3
|
/**
|
|
@@ -126,10 +126,10 @@ export interface CollectionChanges<T = any> {
|
|
|
126
126
|
* Possible states for a 1:1 relationship.
|
|
127
127
|
*/
|
|
128
128
|
export type EntityChangeState =
|
|
129
|
-
| "created"
|
|
130
|
-
| "updated"
|
|
131
|
-
| "deleted"
|
|
132
|
-
| "replaced"
|
|
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
133
|
| "unchanged"; // No changes
|
|
134
134
|
|
|
135
135
|
/**
|
|
@@ -219,3 +219,15 @@ export interface TrackedEntityMetadata {
|
|
|
219
219
|
/** Path in the object (e.g., 'posts[0].comments[1]') */
|
|
220
220
|
path: string;
|
|
221
221
|
}
|
|
222
|
+
|
|
223
|
+
export interface TrackedItem {
|
|
224
|
+
entity: any;
|
|
225
|
+
metadata: TrackedEntityMetadata;
|
|
226
|
+
originalState: any;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface ArrayState {
|
|
230
|
+
cloned: any[];
|
|
231
|
+
original: any[];
|
|
232
|
+
metadata: TrackedEntityMetadata;
|
|
233
|
+
}
|
package/src/types/domain.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { ValidationConfig } from "..";
|
|
2
1
|
import { Id } from "../id";
|
|
3
2
|
import { StandardSchema } from "./standard-schema";
|
|
4
3
|
|
|
5
|
-
export type EntityId = string | number;
|
|
6
|
-
|
|
7
4
|
export interface BaseProps {
|
|
8
5
|
id: Id;
|
|
9
6
|
}
|
|
@@ -22,15 +19,14 @@ export interface VOHooks<T, E> {
|
|
|
22
19
|
rules?: (entity: E) => void;
|
|
23
20
|
}
|
|
24
21
|
|
|
25
|
-
// Specialized hooks for entities (with BaseProps)
|
|
26
22
|
export interface EntityHooks<T extends BaseProps, E> {
|
|
27
23
|
onBeforeUpdate?: (entity: E, snapshot: T) => boolean;
|
|
28
24
|
onCreate?: (entity: E) => void;
|
|
29
25
|
rules?: (entity: E) => void;
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
export interface
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
export interface ValidationConfig {
|
|
29
|
+
onCreate?: boolean;
|
|
30
|
+
onUpdate?: boolean;
|
|
31
|
+
throwOnError?: boolean;
|
|
36
32
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -8,28 +8,71 @@ import {
|
|
|
8
8
|
StringOperators,
|
|
9
9
|
} from "../types";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const FORCE_STRING_OPERATORS = new Set(["contains", "startsWith", "endsWith"]);
|
|
12
|
+
|
|
13
|
+
export function sanitizeFieldValue(
|
|
12
14
|
value: unknown,
|
|
13
15
|
operator: FilterOperator
|
|
14
|
-
):
|
|
15
|
-
// Handle null/undefined
|
|
16
|
+
): unknown {
|
|
16
17
|
if (value === null || value === undefined) {
|
|
17
|
-
return
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return value.map((item) => sanitizeFieldValue(item, operator));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (value instanceof Date) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const stringValue = String(value).trim();
|
|
30
|
+
|
|
31
|
+
if (stringValue === "") {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (operator && FORCE_STRING_OPERATORS.has(operator)) {
|
|
36
|
+
return stringValue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (stringValue === "true" || stringValue === "false") {
|
|
40
|
+
return stringValue === "true";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const numberValue = Number(stringValue);
|
|
44
|
+
if (!Number.isNaN(numberValue)) {
|
|
45
|
+
return numberValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const dateObj = new Date(stringValue);
|
|
49
|
+
if (!Number.isNaN(dateObj.getTime())) {
|
|
50
|
+
return dateObj;
|
|
18
51
|
}
|
|
19
52
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
53
|
+
return stringValue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isValidOperatorForType(
|
|
57
|
+
value: unknown,
|
|
58
|
+
operator: FilterOperator
|
|
59
|
+
): boolean {
|
|
60
|
+
const sanitizedValue = sanitizeFieldValue(value, operator);
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
operator === "between" &&
|
|
64
|
+
Array.isArray(sanitizedValue) &&
|
|
65
|
+
sanitizedValue.length === 2
|
|
66
|
+
) {
|
|
67
|
+
const elementType = typeof sanitizedValue[0];
|
|
68
|
+
if (elementType === "number" || sanitizedValue[0] instanceof Date) {
|
|
25
69
|
return true;
|
|
26
70
|
}
|
|
27
71
|
return false;
|
|
28
72
|
}
|
|
29
73
|
|
|
30
|
-
const valueType = typeof
|
|
74
|
+
const valueType = typeof sanitizedValue;
|
|
31
75
|
|
|
32
|
-
// String operators
|
|
33
76
|
if (valueType === "string") {
|
|
34
77
|
const validOps: StringOperators[] = [
|
|
35
78
|
"equals",
|
|
@@ -45,7 +88,6 @@ export function isValidOperatorForType(
|
|
|
45
88
|
return validOps.includes(operator as StringOperators);
|
|
46
89
|
}
|
|
47
90
|
|
|
48
|
-
// Number operators
|
|
49
91
|
if (valueType === "number") {
|
|
50
92
|
const validOps: NumberOperators[] = [
|
|
51
93
|
"equals",
|
|
@@ -63,7 +105,6 @@ export function isValidOperatorForType(
|
|
|
63
105
|
return validOps.includes(operator as NumberOperators);
|
|
64
106
|
}
|
|
65
107
|
|
|
66
|
-
// Boolean operators
|
|
67
108
|
if (valueType === "boolean") {
|
|
68
109
|
const validOps: BooleanOperators[] = [
|
|
69
110
|
"equals",
|
|
@@ -74,8 +115,7 @@ export function isValidOperatorForType(
|
|
|
74
115
|
return validOps.includes(operator as BooleanOperators);
|
|
75
116
|
}
|
|
76
117
|
|
|
77
|
-
|
|
78
|
-
if (value instanceof Date) {
|
|
118
|
+
if (sanitizedValue instanceof Date) {
|
|
79
119
|
const validOps: DateOperators[] = [
|
|
80
120
|
"equals",
|
|
81
121
|
"notEquals",
|
|
@@ -92,13 +132,11 @@ export function isValidOperatorForType(
|
|
|
92
132
|
return validOps.includes(operator as DateOperators);
|
|
93
133
|
}
|
|
94
134
|
|
|
95
|
-
|
|
96
|
-
if (Array.isArray(value)) {
|
|
135
|
+
if (Array.isArray(sanitizedValue)) {
|
|
97
136
|
const validOps: ArrayOperators[] = ["in", "notIn", "isNull", "isNotNull"];
|
|
98
137
|
return validOps.includes(operator as ArrayOperators);
|
|
99
138
|
}
|
|
100
139
|
|
|
101
|
-
// For unknown types, allow all operators
|
|
102
140
|
return true;
|
|
103
141
|
}
|
|
104
142
|
|
|
@@ -168,4 +206,4 @@ export function getValidOperatorsForType(value: unknown): FilterOperator[] {
|
|
|
168
206
|
|
|
169
207
|
export function isOperator(value: string): value is FilterOperator {
|
|
170
208
|
return FILTER_OPERATORS.includes(value as FilterOperator);
|
|
171
|
-
}
|
|
209
|
+
}
|
package/src/validation-error.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface ValidationIssue {
|
|
|
5
5
|
|
|
6
6
|
export class ValidationError extends Error {
|
|
7
7
|
public readonly issues: ValidationIssue[];
|
|
8
|
-
public readonly __isValidationError = true;
|
|
8
|
+
public readonly __isValidationError = true;
|
|
9
9
|
|
|
10
10
|
constructor(issues: ValidationIssue[], message?: string) {
|
|
11
11
|
const errorMessage =
|
|
@@ -14,7 +14,6 @@ export class ValidationError extends Error {
|
|
|
14
14
|
this.name = 'ValidationError';
|
|
15
15
|
this.issues = issues;
|
|
16
16
|
|
|
17
|
-
// Maintain proper stack trace
|
|
18
17
|
if (Error.captureStackTrace) {
|
|
19
18
|
Error.captureStackTrace(this, ValidationError);
|
|
20
19
|
}
|
|
@@ -27,7 +26,6 @@ export class ValidationError extends Error {
|
|
|
27
26
|
if (error instanceof ValidationError) {
|
|
28
27
|
return true;
|
|
29
28
|
}
|
|
30
|
-
// Check by duck typing for cross-module compatibility
|
|
31
29
|
return (
|
|
32
30
|
error instanceof Error &&
|
|
33
31
|
error.name === 'ValidationError' &&
|
package/src/value-object.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
import { DEFAULT_VALIDATION_CONFIG } from "./constants";
|
|
10
10
|
import { DomainError } from "./exceptions";
|
|
11
11
|
|
|
12
|
-
// Helper to get static properties from constructor
|
|
13
12
|
function getStaticProperty<T>(
|
|
14
13
|
instance: any,
|
|
15
14
|
propertyName: string
|
|
@@ -34,7 +33,7 @@ export abstract class ValueObject<T> {
|
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
35
|
* Identity key for identification in collections.
|
|
37
|
-
* Used by
|
|
36
|
+
* Used by ChangeTracker to track changes in arrays of Value Objects.
|
|
38
37
|
*
|
|
39
38
|
* @example
|
|
40
39
|
* ```typescript
|