@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/criteria.ts
CHANGED
|
@@ -1,68 +1,94 @@
|
|
|
1
1
|
import { InvalidCriteriaError } from "./exceptions";
|
|
2
2
|
import {
|
|
3
|
+
CriteriaAdapter,
|
|
4
|
+
CriteriaOptions,
|
|
3
5
|
FieldPath,
|
|
4
6
|
Filter,
|
|
5
|
-
FILTER_OPERATORS,
|
|
6
7
|
FilterOperator,
|
|
7
8
|
FilterValueFor,
|
|
9
|
+
OperatorsForType,
|
|
8
10
|
Order,
|
|
9
11
|
OrderDirection,
|
|
10
12
|
Pagination,
|
|
11
13
|
PathValue,
|
|
12
14
|
Search,
|
|
13
15
|
TypedFilter,
|
|
16
|
+
TypedOrder,
|
|
14
17
|
} from "./types";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
import {
|
|
19
|
+
isValidOperatorForType,
|
|
20
|
+
getValidOperatorsForType,
|
|
21
|
+
isOperator,
|
|
22
|
+
} from "./utils/criteria-operator-validation";
|
|
23
|
+
import { parseQueryValue } from "./utils/helpers";
|
|
19
24
|
|
|
20
25
|
export class Criteria<T = any> {
|
|
21
26
|
private _filters: Filter<FieldPath<T>, any>[] = [];
|
|
22
27
|
private _orders: Order[] = [];
|
|
23
28
|
private _pagination: Pagination = { page: 1, limit: 20, offset: 0 };
|
|
24
|
-
private _search?: Search<T
|
|
29
|
+
private _search?: Search<T>;
|
|
30
|
+
private _adapter?: CriteriaAdapter<any, any>;
|
|
25
31
|
|
|
26
32
|
private constructor() {}
|
|
27
33
|
|
|
28
|
-
/**
|
|
29
|
-
* Create a new Criteria instance
|
|
30
|
-
*/
|
|
31
34
|
static create<T = any>(): Criteria<T> {
|
|
32
35
|
return new Criteria<T>();
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
useAdapter<A extends CriteriaAdapter<any, any>>(map: A): this {
|
|
39
|
+
this._adapter = map;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getAdapter(): CriteriaAdapter<any, any> | undefined {
|
|
44
|
+
return this._adapter;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
where<K extends FieldPath<T>>(
|
|
48
|
+
field: K,
|
|
49
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>,
|
|
50
|
+
value?: FilterValueFor<PathValue<T, K>>,
|
|
51
|
+
options?: CriteriaOptions
|
|
52
|
+
): this;
|
|
53
|
+
|
|
38
54
|
where<K extends FieldPath<T>>(
|
|
39
55
|
field: K,
|
|
40
56
|
operator: FilterOperator,
|
|
41
|
-
value?: FilterValueFor<PathValue<T, K
|
|
57
|
+
value?: FilterValueFor<PathValue<T, K>>,
|
|
58
|
+
options?: CriteriaOptions
|
|
42
59
|
): this {
|
|
60
|
+
this.validateOperator(operator, value);
|
|
61
|
+
|
|
43
62
|
this._filters.push({
|
|
44
|
-
field,
|
|
63
|
+
field: this.resolveFieldPath(field),
|
|
45
64
|
operator,
|
|
46
65
|
value,
|
|
66
|
+
options,
|
|
47
67
|
});
|
|
48
68
|
return this;
|
|
49
69
|
}
|
|
50
70
|
|
|
51
|
-
// === Shorthand methods (tipados) ===
|
|
52
|
-
|
|
53
71
|
whereEquals<K extends FieldPath<T>>(field: K, value: PathValue<T, K>): this {
|
|
54
|
-
return this.where(
|
|
72
|
+
return this.where(
|
|
73
|
+
field,
|
|
74
|
+
"equals" as OperatorsForType<PathValue<T, K>>,
|
|
75
|
+
value
|
|
76
|
+
);
|
|
55
77
|
}
|
|
56
78
|
|
|
57
79
|
whereContains<K extends FieldPath<T>>(
|
|
58
80
|
field: K,
|
|
59
81
|
value: PathValue<T, K>
|
|
60
82
|
): this {
|
|
61
|
-
return this.where(
|
|
83
|
+
return this.where(
|
|
84
|
+
field,
|
|
85
|
+
"contains" as OperatorsForType<PathValue<T, K>>,
|
|
86
|
+
value
|
|
87
|
+
);
|
|
62
88
|
}
|
|
63
89
|
|
|
64
90
|
whereIn<K extends FieldPath<T>>(field: K, values: PathValue<T, K>[]): this {
|
|
65
|
-
return this.where(field, "in", values);
|
|
91
|
+
return this.where(field, "in" as OperatorsForType<PathValue<T, K>>, values);
|
|
66
92
|
}
|
|
67
93
|
|
|
68
94
|
whereBetween<K extends FieldPath<T>>(
|
|
@@ -70,28 +96,51 @@ export class Criteria<T = any> {
|
|
|
70
96
|
min: PathValue<T, K>,
|
|
71
97
|
max: PathValue<T, K>
|
|
72
98
|
): this {
|
|
73
|
-
return this.where(
|
|
74
|
-
|
|
75
|
-
PathValue<T, K
|
|
76
|
-
|
|
99
|
+
return this.where(
|
|
100
|
+
field,
|
|
101
|
+
"between" as OperatorsForType<PathValue<T, K>>,
|
|
102
|
+
[min, max] as [PathValue<T, K>, PathValue<T, K>]
|
|
103
|
+
);
|
|
77
104
|
}
|
|
78
105
|
|
|
79
106
|
whereNull<K extends FieldPath<T>>(field: K): this {
|
|
80
|
-
return this.where(field, "isNull");
|
|
107
|
+
return this.where(field, "isNull" as OperatorsForType<PathValue<T, K>>);
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
whereNotNull<K extends FieldPath<T>>(field: K): this {
|
|
84
|
-
return this.where(field, "isNotNull");
|
|
111
|
+
return this.where(field, "isNotNull" as OperatorsForType<PathValue<T, K>>);
|
|
85
112
|
}
|
|
86
113
|
|
|
87
|
-
|
|
114
|
+
whereSome<K extends FieldPath<T>>(
|
|
115
|
+
field: K,
|
|
116
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>,
|
|
117
|
+
value?: FilterValueFor<PathValue<T, K>>
|
|
118
|
+
): this {
|
|
119
|
+
return this.where(field, operator, value, { quantifier: "some" });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
whereEvery<K extends FieldPath<T>>(
|
|
123
|
+
field: K,
|
|
124
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>,
|
|
125
|
+
value?: FilterValueFor<PathValue<T, K>>
|
|
126
|
+
): this {
|
|
127
|
+
return this.where(field, operator, value, { quantifier: "every" });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
whereNone<K extends FieldPath<T>>(
|
|
131
|
+
field: K,
|
|
132
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>,
|
|
133
|
+
value?: FilterValueFor<PathValue<T, K>>
|
|
134
|
+
): this {
|
|
135
|
+
return this.where(field, operator, value, { quantifier: "none" });
|
|
136
|
+
}
|
|
88
137
|
|
|
89
138
|
orderBy<K extends FieldPath<T>>(
|
|
90
139
|
field: K,
|
|
91
140
|
direction: OrderDirection = "asc"
|
|
92
141
|
): this {
|
|
93
142
|
this._orders.push({
|
|
94
|
-
field:
|
|
143
|
+
field: this.resolveFieldPath(field),
|
|
95
144
|
direction,
|
|
96
145
|
});
|
|
97
146
|
return this;
|
|
@@ -105,13 +154,9 @@ export class Criteria<T = any> {
|
|
|
105
154
|
return this.orderBy(field, "desc");
|
|
106
155
|
}
|
|
107
156
|
|
|
108
|
-
// --------------------------------------------------------------------------
|
|
109
|
-
// Search (tipado)
|
|
110
|
-
// --------------------------------------------------------------------------
|
|
111
|
-
|
|
112
157
|
search<K extends FieldPath<T>>(fields: K[], value: string): this {
|
|
113
158
|
this._search = {
|
|
114
|
-
fields,
|
|
159
|
+
fields: fields.map(this.resolveFieldPath),
|
|
115
160
|
value,
|
|
116
161
|
};
|
|
117
162
|
return this;
|
|
@@ -122,11 +167,14 @@ export class Criteria<T = any> {
|
|
|
122
167
|
}
|
|
123
168
|
|
|
124
169
|
getSearch() {
|
|
125
|
-
return this._search
|
|
170
|
+
return this._search
|
|
171
|
+
? {
|
|
172
|
+
fields: this._search.fields.map(this.resolveFieldPath),
|
|
173
|
+
value: this._search.value,
|
|
174
|
+
}
|
|
175
|
+
: undefined;
|
|
126
176
|
}
|
|
127
177
|
|
|
128
|
-
// === Pagination ===
|
|
129
|
-
|
|
130
178
|
paginate(page: number, limit: number): this {
|
|
131
179
|
if (page < 1) page = 1;
|
|
132
180
|
if (limit < 1) limit = 10;
|
|
@@ -143,14 +191,20 @@ export class Criteria<T = any> {
|
|
|
143
191
|
return this.paginate(1, limit);
|
|
144
192
|
}
|
|
145
193
|
|
|
146
|
-
// === Getters ===
|
|
147
|
-
|
|
148
194
|
getFilters(): Filter[] {
|
|
149
|
-
return this._filters
|
|
195
|
+
return this._filters.map((filter) => ({
|
|
196
|
+
field: this.resolveFieldPath(filter.field),
|
|
197
|
+
operator: filter.operator,
|
|
198
|
+
value: filter.value,
|
|
199
|
+
options: filter.options,
|
|
200
|
+
}));
|
|
150
201
|
}
|
|
151
202
|
|
|
152
203
|
getOrders(): Order[] {
|
|
153
|
-
return this._orders
|
|
204
|
+
return this._orders.map((order) => ({
|
|
205
|
+
field: this.resolveFieldPath(order.field as FieldPath<T>),
|
|
206
|
+
direction: order.direction,
|
|
207
|
+
}));
|
|
154
208
|
}
|
|
155
209
|
|
|
156
210
|
getPagination(): Pagination {
|
|
@@ -169,47 +223,132 @@ export class Criteria<T = any> {
|
|
|
169
223
|
return this._pagination !== undefined;
|
|
170
224
|
}
|
|
171
225
|
|
|
172
|
-
// === Utilities ===
|
|
173
|
-
|
|
174
226
|
clone(): Criteria<T> {
|
|
175
227
|
const cloned = Criteria.create<T>();
|
|
176
|
-
cloned._filters = [
|
|
177
|
-
|
|
228
|
+
cloned._filters = [
|
|
229
|
+
...this._filters.map((filter) => ({
|
|
230
|
+
field: this.resolveFieldPath(filter.field),
|
|
231
|
+
operator: filter.operator,
|
|
232
|
+
value: filter.value,
|
|
233
|
+
options: filter.options,
|
|
234
|
+
})),
|
|
235
|
+
];
|
|
236
|
+
cloned._orders = [
|
|
237
|
+
...this._orders.map((order) => ({
|
|
238
|
+
field: this.resolveFieldPath(order.field as FieldPath<T>),
|
|
239
|
+
direction: order.direction,
|
|
240
|
+
})),
|
|
241
|
+
];
|
|
178
242
|
cloned._pagination = { ...this._pagination };
|
|
243
|
+
cloned._search = this._search
|
|
244
|
+
? {
|
|
245
|
+
fields: this._search.fields.map(this.resolveFieldPath),
|
|
246
|
+
value: this._search.value,
|
|
247
|
+
}
|
|
248
|
+
: undefined;
|
|
249
|
+
|
|
250
|
+
if (this._adapter) {
|
|
251
|
+
cloned.useAdapter(this._adapter);
|
|
252
|
+
}
|
|
253
|
+
|
|
179
254
|
return cloned;
|
|
180
255
|
}
|
|
181
256
|
|
|
182
257
|
toJSON() {
|
|
183
258
|
return {
|
|
184
|
-
filters: this._filters
|
|
185
|
-
|
|
259
|
+
filters: this._filters.map((filter) => ({
|
|
260
|
+
field: this.resolveFieldPath(filter.field),
|
|
261
|
+
operator: filter.operator,
|
|
262
|
+
value: filter.value,
|
|
263
|
+
options: filter.options,
|
|
264
|
+
})),
|
|
265
|
+
orders: this._orders.map((order) => ({
|
|
266
|
+
field: this.resolveFieldPath(order.field as FieldPath<T>),
|
|
267
|
+
direction: order.direction,
|
|
268
|
+
})),
|
|
186
269
|
pagination: this._pagination,
|
|
187
|
-
search: this._search
|
|
270
|
+
search: this._search
|
|
271
|
+
? {
|
|
272
|
+
fields: this._search.fields.map(this.resolveFieldPath),
|
|
273
|
+
value: this._search.value,
|
|
274
|
+
}
|
|
275
|
+
: undefined,
|
|
188
276
|
};
|
|
189
277
|
}
|
|
190
278
|
|
|
191
|
-
static fromObject<T>(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
279
|
+
static fromObject<T>(
|
|
280
|
+
obj: {
|
|
281
|
+
filters?: TypedFilter<T>[];
|
|
282
|
+
orders?: TypedOrder<T>[];
|
|
283
|
+
pagination?: Pagination;
|
|
284
|
+
search?: { fields: FieldPath<T>[]; value: string };
|
|
285
|
+
},
|
|
286
|
+
adapter?: CriteriaAdapter<any, any>
|
|
287
|
+
): Criteria<T> {
|
|
197
288
|
const criteria = Criteria.create<T>();
|
|
198
|
-
|
|
199
|
-
if (
|
|
289
|
+
|
|
290
|
+
if (adapter) {
|
|
291
|
+
criteria.useAdapter(adapter);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (obj.filters) {
|
|
295
|
+
for (const filter of obj.filters) {
|
|
296
|
+
filter.field = criteria.resolveFieldPath(filter.field);
|
|
297
|
+
criteria.validateOperator(filter.operator, filter.value);
|
|
298
|
+
}
|
|
299
|
+
criteria._filters = [...obj.filters];
|
|
300
|
+
}
|
|
301
|
+
if (obj.orders)
|
|
302
|
+
criteria._orders = [
|
|
303
|
+
...obj.orders.map((order) => ({
|
|
304
|
+
field: criteria.resolveFieldPath(order.field as FieldPath<T>),
|
|
305
|
+
direction: order.direction,
|
|
306
|
+
})),
|
|
307
|
+
];
|
|
200
308
|
if (obj.pagination) criteria._pagination = { ...obj.pagination };
|
|
201
|
-
if (obj.search)
|
|
309
|
+
if (obj.search)
|
|
310
|
+
criteria._search = {
|
|
311
|
+
...obj.search,
|
|
312
|
+
fields: obj.search.fields.map(criteria.resolveFieldPath),
|
|
313
|
+
};
|
|
202
314
|
|
|
203
315
|
return criteria;
|
|
204
316
|
}
|
|
205
317
|
|
|
206
|
-
|
|
318
|
+
protected resolveFieldPath(field: FieldPath<T>): FieldPath<T> {
|
|
319
|
+
if (!this?._adapter) return field;
|
|
320
|
+
|
|
321
|
+
if (this._adapter[field]) {
|
|
322
|
+
return this._adapter[field] as FieldPath<T>;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const parts = field.split(".");
|
|
326
|
+
for (let i = parts.length; i > 0; i--) {
|
|
327
|
+
const prefix = parts.slice(0, i).join(".");
|
|
328
|
+
if (this._adapter[prefix]) {
|
|
329
|
+
const rest = parts.slice(i).join(".");
|
|
330
|
+
return rest
|
|
331
|
+
? (`${this._adapter[prefix]}.${rest}` as FieldPath<T>)
|
|
332
|
+
: (this._adapter[prefix] as FieldPath<T>);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return field;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
static fromQueryParams<T>(
|
|
340
|
+
query: Record<string, any>,
|
|
341
|
+
adapter?: CriteriaAdapter<any, any>
|
|
342
|
+
): Criteria<T> {
|
|
207
343
|
const criteria = Criteria.create<T>();
|
|
208
344
|
|
|
345
|
+
if (adapter) {
|
|
346
|
+
criteria.useAdapter(adapter);
|
|
347
|
+
}
|
|
348
|
+
|
|
209
349
|
for (const [key, value] of Object.entries(query)) {
|
|
210
|
-
// Pagination
|
|
211
350
|
if (key === "page") {
|
|
212
|
-
continue;
|
|
351
|
+
continue;
|
|
213
352
|
}
|
|
214
353
|
if (key === "limit") {
|
|
215
354
|
continue;
|
|
@@ -218,35 +357,78 @@ export class Criteria<T = any> {
|
|
|
218
357
|
continue;
|
|
219
358
|
}
|
|
220
359
|
|
|
221
|
-
const [field,
|
|
360
|
+
const [field, operatorWithQuantifier] = key.split(":");
|
|
222
361
|
|
|
223
|
-
if (!
|
|
362
|
+
if (!operatorWithQuantifier || !field) continue;
|
|
363
|
+
|
|
364
|
+
const [operatorRaw, quantifierRaw] = operatorWithQuantifier.split("@");
|
|
224
365
|
const operator = isOperator(operatorRaw) ? operatorRaw : null;
|
|
225
|
-
if (!operator)
|
|
366
|
+
if (!operator) {
|
|
226
367
|
throw new InvalidCriteriaError(`Invalid filter operator`, operatorRaw);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const validQuantifiers = ["some", "every", "none"];
|
|
371
|
+
const quantifier =
|
|
372
|
+
quantifierRaw && validQuantifiers.includes(quantifierRaw)
|
|
373
|
+
? (quantifierRaw as CriteriaOptions["quantifier"])
|
|
374
|
+
: undefined;
|
|
375
|
+
|
|
376
|
+
if (quantifierRaw && !quantifier) {
|
|
377
|
+
throw new InvalidCriteriaError(
|
|
378
|
+
`Invalid quantifier. Valid values: ${validQuantifiers.join(", ")}`,
|
|
379
|
+
quantifierRaw
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const options: CriteriaOptions | undefined = quantifier
|
|
384
|
+
? { quantifier }
|
|
385
|
+
: undefined;
|
|
227
386
|
|
|
228
387
|
let parsedValue: any = value;
|
|
229
388
|
|
|
389
|
+
const resolvedField = criteria.resolveFieldPath(field as FieldPath<T>);
|
|
390
|
+
|
|
230
391
|
if (operator === "between") {
|
|
231
392
|
parsedValue = value
|
|
232
393
|
.split(",")
|
|
233
394
|
.map((v: any) => parseQueryValue(v.trim()));
|
|
234
395
|
if (parsedValue.length === 2) {
|
|
235
|
-
criteria.
|
|
396
|
+
criteria.where(
|
|
397
|
+
resolvedField,
|
|
398
|
+
"between" as OperatorsForType<PathValue<T, FieldPath<T>>>,
|
|
399
|
+
[parsedValue[0], parsedValue[1]] as [
|
|
400
|
+
PathValue<T, FieldPath<T>>,
|
|
401
|
+
PathValue<T, FieldPath<T>>
|
|
402
|
+
],
|
|
403
|
+
options
|
|
404
|
+
);
|
|
236
405
|
}
|
|
237
406
|
continue;
|
|
238
407
|
}
|
|
239
408
|
|
|
240
409
|
if (operator === "in" || operator === "notIn") {
|
|
241
410
|
parsedValue = value.split(",").map(parseQueryValue);
|
|
242
|
-
criteria.where(
|
|
411
|
+
criteria.where(
|
|
412
|
+
field as any,
|
|
413
|
+
operator as OperatorsForType<PathValue<T, FieldPath<T>>>,
|
|
414
|
+
parsedValue,
|
|
415
|
+
options
|
|
416
|
+
);
|
|
243
417
|
continue;
|
|
244
418
|
}
|
|
245
419
|
|
|
246
|
-
|
|
420
|
+
const parsedFinalValue = parseQueryValue(value);
|
|
421
|
+
|
|
422
|
+
criteria.validateOperator(operator, parsedFinalValue);
|
|
423
|
+
|
|
424
|
+
criteria.where(
|
|
425
|
+
field as FieldPath<T>,
|
|
426
|
+
operator as OperatorsForType<PathValue<T, FieldPath<T>>>,
|
|
427
|
+
parsedFinalValue,
|
|
428
|
+
options
|
|
429
|
+
);
|
|
247
430
|
}
|
|
248
431
|
|
|
249
|
-
// Pagination
|
|
250
432
|
const page = query.page ? parseInt(query.page) : undefined;
|
|
251
433
|
const limit = query.limit ? parseInt(query.limit) : undefined;
|
|
252
434
|
|
|
@@ -254,12 +436,14 @@ export class Criteria<T = any> {
|
|
|
254
436
|
criteria.paginate(page, limit);
|
|
255
437
|
}
|
|
256
438
|
|
|
257
|
-
// Sorting
|
|
258
439
|
if (query.orderBy) {
|
|
259
440
|
const sortParts = query.orderBy.split(",");
|
|
260
441
|
sortParts.forEach((part: string) => {
|
|
261
442
|
const [field, direction] = part.split(":");
|
|
262
|
-
criteria.orderBy(
|
|
443
|
+
criteria.orderBy(
|
|
444
|
+
field as FieldPath<T>,
|
|
445
|
+
(direction as OrderDirection) || "asc"
|
|
446
|
+
);
|
|
263
447
|
});
|
|
264
448
|
}
|
|
265
449
|
|
|
@@ -268,24 +452,22 @@ export class Criteria<T = any> {
|
|
|
268
452
|
.split(",")
|
|
269
453
|
.filter(Boolean) as FieldPath<T>[];
|
|
270
454
|
|
|
271
|
-
|
|
455
|
+
const resolvedFields = fields.map(criteria.resolveFieldPath);
|
|
456
|
+
criteria.search(resolvedFields, query.search as string);
|
|
272
457
|
}
|
|
273
458
|
|
|
274
459
|
return criteria;
|
|
275
460
|
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ============================================================================
|
|
279
|
-
// Helper Functions
|
|
280
|
-
// ============================================================================
|
|
281
461
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
462
|
+
private validateOperator(operator: FilterOperator, value: any): void {
|
|
463
|
+
if (value !== undefined && !isValidOperatorForType(value, operator)) {
|
|
464
|
+
const validOps = getValidOperatorsForType(value);
|
|
465
|
+
throw new InvalidCriteriaError(
|
|
466
|
+
`Operator "${operator}" is not valid for type "${typeof value}". Valid operators: ${validOps.join(
|
|
467
|
+
", "
|
|
468
|
+
)}`,
|
|
469
|
+
operator
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
291
473
|
}
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const customCrypto = {
|
|
2
|
+
randomUUID: () => {
|
|
3
|
+
if (
|
|
4
|
+
typeof globalThis !== "undefined" &&
|
|
5
|
+
globalThis?.crypto &&
|
|
6
|
+
typeof globalThis.crypto.randomUUID === "function"
|
|
7
|
+
) {
|
|
8
|
+
return globalThis.crypto.randomUUID();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const hexChars = "0123456789abcdef";
|
|
12
|
+
let uuid = "";
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < 36; i++) {
|
|
15
|
+
if (i === 8 || i === 13 || i === 18 || i === 23) {
|
|
16
|
+
uuid += "-";
|
|
17
|
+
} else if (i === 14) {
|
|
18
|
+
uuid += "4";
|
|
19
|
+
} else if (i === 19) {
|
|
20
|
+
uuid += hexChars.charAt(Math.floor(Math.random() * 4) + 8);
|
|
21
|
+
} else {
|
|
22
|
+
uuid += hexChars.charAt(Math.floor(Math.random() * 16));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return uuid;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const UUID = customCrypto.randomUUID;
|
|
31
|
+
export default UUID;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Entity Changes
|
|
3
|
+
// Changes filtered by entity type
|
|
4
|
+
// ============================================================================
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Operation,
|
|
8
|
+
CreateOperation,
|
|
9
|
+
UpdateOperation,
|
|
10
|
+
DeleteOperation,
|
|
11
|
+
} from "./types/change-tracker";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents the changes filtered for a specific entity.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const changes = user.getChanges();
|
|
19
|
+
* const postChanges = changes.for('Post');
|
|
20
|
+
*
|
|
21
|
+
* if (postChanges.hasCreates()) {
|
|
22
|
+
* console.log('Created posts:', postChanges.creates);
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* if (postChanges.hasUpdates()) {
|
|
26
|
+
* postChanges.updates.forEach(({ entity, changed }) => {
|
|
27
|
+
* console.log(`Post ${entity.id} has changed:`, changed);
|
|
28
|
+
* });
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class EntityChanges<T = any> {
|
|
33
|
+
constructor(private readonly operations: Operation<T>[]) {}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns all created entities
|
|
37
|
+
*/
|
|
38
|
+
get creates(): T[] {
|
|
39
|
+
return this.operations
|
|
40
|
+
.filter((op): op is CreateOperation<T> => op.type === "create")
|
|
41
|
+
.map((op) => op.data);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns all updated entities with their changed fields
|
|
46
|
+
*/
|
|
47
|
+
get updates(): Array<{ entity: T; changed: Record<string, any> }> {
|
|
48
|
+
return this.operations
|
|
49
|
+
.filter((op): op is UpdateOperation<T> => op.type === "update")
|
|
50
|
+
.map((op) => ({
|
|
51
|
+
entity: op.data,
|
|
52
|
+
changed: op.changedFields,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns all deleted entities
|
|
58
|
+
*/
|
|
59
|
+
get deletes(): T[] {
|
|
60
|
+
return this.operations
|
|
61
|
+
.filter((op): op is DeleteOperation<T> => op.type === "delete")
|
|
62
|
+
.map((op) => op.data);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns the IDs of the created entities
|
|
67
|
+
*/
|
|
68
|
+
get createIds(): string[] {
|
|
69
|
+
return this.operations
|
|
70
|
+
.filter((op): op is CreateOperation<T> => op.type === "create")
|
|
71
|
+
.map((op) => this.extractId(op.data))
|
|
72
|
+
.filter((id): id is string => id !== undefined);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns the IDs of the updated entities
|
|
77
|
+
*/
|
|
78
|
+
get updateIds(): string[] {
|
|
79
|
+
return this.operations
|
|
80
|
+
.filter((op): op is UpdateOperation<T> => op.type === "update")
|
|
81
|
+
.map((op) => op.id);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns the IDs of the deleted entities
|
|
86
|
+
*/
|
|
87
|
+
get deleteIds(): string[] {
|
|
88
|
+
return this.operations
|
|
89
|
+
.filter((op): op is DeleteOperation<T> => op.type === "delete")
|
|
90
|
+
.map((op) => op.id);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Checks if there are any creates
|
|
95
|
+
*/
|
|
96
|
+
hasCreates(): boolean {
|
|
97
|
+
return this.creates.length > 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Checks if there are any updates
|
|
102
|
+
*/
|
|
103
|
+
hasUpdates(): boolean {
|
|
104
|
+
return this.updates.length > 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Checks if there are any deletes
|
|
109
|
+
*/
|
|
110
|
+
hasDeletes(): boolean {
|
|
111
|
+
return this.deletes.length > 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Checks if there is any change
|
|
116
|
+
*/
|
|
117
|
+
hasChanges(): boolean {
|
|
118
|
+
return this.operations.length > 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Checks if it is empty (no changes)
|
|
123
|
+
*/
|
|
124
|
+
isEmpty(): boolean {
|
|
125
|
+
return this.operations.length === 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Returns the total number of operations
|
|
130
|
+
*/
|
|
131
|
+
get count(): number {
|
|
132
|
+
return this.operations.length;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Returns the raw operations (for advanced use cases)
|
|
137
|
+
*/
|
|
138
|
+
get rawOperations(): Operation<T>[] {
|
|
139
|
+
return [...this.operations];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Extracts the ID from an entity
|
|
144
|
+
*/
|
|
145
|
+
private extractId(entity: any): string | undefined {
|
|
146
|
+
if (!entity) return undefined;
|
|
147
|
+
if (entity.id?.value) return entity.id.value;
|
|
148
|
+
if (typeof entity.id === "string") return entity.id;
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
}
|