@woltz/rich-domain 1.9.1 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/aggregate-changes.d.ts +14 -0
- package/dist/cjs/core/aggregate-changes.d.ts.map +1 -1
- package/dist/cjs/core/aggregate-changes.js +18 -0
- package/dist/cjs/core/aggregate-changes.js.map +1 -1
- package/dist/cjs/core/base-entity.d.ts +2 -0
- package/dist/cjs/core/base-entity.d.ts.map +1 -1
- package/dist/cjs/core/base-entity.js +39 -41
- package/dist/cjs/core/base-entity.js.map +1 -1
- package/dist/cjs/core/change-tracker.d.ts +8 -0
- package/dist/cjs/core/change-tracker.d.ts.map +1 -1
- package/dist/cjs/core/change-tracker.js +36 -6
- package/dist/cjs/core/change-tracker.js.map +1 -1
- package/dist/cjs/core/value-object.d.ts.map +1 -1
- package/dist/cjs/core/value-object.js +3 -5
- package/dist/cjs/core/value-object.js.map +1 -1
- package/dist/cjs/repository/entity-schema-registry.d.ts +56 -3
- package/dist/cjs/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/cjs/repository/entity-schema-registry.js +61 -6
- package/dist/cjs/repository/entity-schema-registry.js.map +1 -1
- package/dist/cjs/utils/helpers.d.ts +1 -0
- package/dist/cjs/utils/helpers.d.ts.map +1 -1
- package/dist/cjs/utils/helpers.js +4 -0
- package/dist/cjs/utils/helpers.js.map +1 -1
- package/dist/esm/core/aggregate-changes.d.ts +14 -0
- package/dist/esm/core/aggregate-changes.d.ts.map +1 -1
- package/dist/esm/core/aggregate-changes.js +18 -0
- package/dist/esm/core/aggregate-changes.js.map +1 -1
- package/dist/esm/core/base-entity.d.ts +2 -0
- package/dist/esm/core/base-entity.d.ts.map +1 -1
- package/dist/esm/core/base-entity.js +37 -39
- package/dist/esm/core/base-entity.js.map +1 -1
- package/dist/esm/core/change-tracker.d.ts +8 -0
- package/dist/esm/core/change-tracker.d.ts.map +1 -1
- package/dist/esm/core/change-tracker.js +36 -6
- package/dist/esm/core/change-tracker.js.map +1 -1
- package/dist/esm/core/value-object.d.ts.map +1 -1
- package/dist/esm/core/value-object.js +1 -3
- package/dist/esm/core/value-object.js.map +1 -1
- package/dist/esm/repository/entity-schema-registry.d.ts +56 -3
- package/dist/esm/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/esm/repository/entity-schema-registry.js +61 -6
- package/dist/esm/repository/entity-schema-registry.js.map +1 -1
- package/dist/esm/utils/helpers.d.ts +1 -0
- package/dist/esm/utils/helpers.d.ts.map +1 -1
- package/dist/esm/utils/helpers.js +3 -0
- package/dist/esm/utils/helpers.js.map +1 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/core/aggregate-changes.d.ts +14 -0
- package/dist/types/core/aggregate-changes.d.ts.map +1 -1
- package/dist/types/core/base-entity.d.ts +2 -0
- package/dist/types/core/base-entity.d.ts.map +1 -1
- package/dist/types/core/change-tracker.d.ts +8 -0
- package/dist/types/core/change-tracker.d.ts.map +1 -1
- package/dist/types/core/value-object.d.ts.map +1 -1
- package/dist/types/repository/entity-schema-registry.d.ts +56 -3
- package/dist/types/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/types/utils/helpers.d.ts +1 -0
- package/dist/types/utils/helpers.d.ts.map +1 -1
- package/package.json +68 -67
- package/src/constants.ts +82 -0
- package/src/core/aggregate-changes.ts +466 -0
- package/src/core/base-aggregate.ts +76 -0
- package/src/core/base-entity.ts +552 -0
- package/src/core/change-tracker.ts +1327 -0
- package/src/core/domain-event.ts +41 -0
- package/src/core/entity-changes.ts +146 -0
- package/src/core/entity.ts +13 -0
- package/src/core/id.ts +124 -0
- package/src/core/index.ts +9 -0
- package/src/core/value-object.ts +179 -0
- package/src/criteria.ts +574 -0
- package/src/exceptions.ts +549 -0
- package/src/index.ts +74 -0
- package/src/repository/base-repository.ts +81 -0
- package/src/repository/entity-schema-registry.ts +620 -0
- package/src/repository/index.ts +5 -0
- package/src/repository/mapper.ts +7 -0
- package/src/repository/paginated-result.ts +251 -0
- package/src/repository/unit-of-work.ts +76 -0
- package/src/types/change-tracker.ts +268 -0
- package/src/types/criteria.ts +197 -0
- package/src/types/domain-event.ts +29 -0
- package/src/types/domain.ts +41 -0
- package/src/types/event-bus.ts +17 -0
- package/src/types/index.ts +9 -0
- package/src/types/outbox-store.ts +97 -0
- package/src/types/standard-schema.ts +19 -0
- package/src/types/unit-of-work.ts +46 -0
- package/src/types/utils.ts +24 -0
- package/src/utils/criteria-operator-validation.ts +209 -0
- package/src/utils/crypto.ts +31 -0
- package/src/utils/helpers.ts +50 -0
- package/src/validation-error.ts +219 -0
package/src/criteria.ts
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import { InvalidCriteriaError } from "./exceptions.js";
|
|
2
|
+
import {
|
|
3
|
+
CriteriaAdapter,
|
|
4
|
+
CriteriaOptions,
|
|
5
|
+
FieldPath,
|
|
6
|
+
Filter,
|
|
7
|
+
FilterOperator,
|
|
8
|
+
FilterValueFor,
|
|
9
|
+
OperatorsForType,
|
|
10
|
+
Order,
|
|
11
|
+
OrderDirection,
|
|
12
|
+
Pagination,
|
|
13
|
+
PathValue,
|
|
14
|
+
QueryParamsObject,
|
|
15
|
+
Search,
|
|
16
|
+
TypedFilter,
|
|
17
|
+
TypedOrder,
|
|
18
|
+
} from "./types/index.js";
|
|
19
|
+
import {
|
|
20
|
+
isValidOperatorForType,
|
|
21
|
+
getValidOperatorsForType,
|
|
22
|
+
sanitizeFieldValue,
|
|
23
|
+
isOperator,
|
|
24
|
+
} from "./utils/criteria-operator-validation.js";
|
|
25
|
+
import { parseQueryValue } from "./utils/helpers.js";
|
|
26
|
+
|
|
27
|
+
export class Criteria<T = any> {
|
|
28
|
+
private _filters: Filter<FieldPath<T>, any>[] = [];
|
|
29
|
+
private _orders: Order[] = [];
|
|
30
|
+
private _pagination: Pagination = { page: 1, limit: 20, offset: 0 };
|
|
31
|
+
private _search?: Search;
|
|
32
|
+
private _adapter?: CriteriaAdapter<any, any>;
|
|
33
|
+
|
|
34
|
+
private constructor() {}
|
|
35
|
+
|
|
36
|
+
static create<T = any>(): Criteria<T> {
|
|
37
|
+
return new Criteria<T>();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
useAdapter<A extends CriteriaAdapter<any, any>>(map: A): this {
|
|
41
|
+
this._adapter = map;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getAdapter(): CriteriaAdapter<any, any> | undefined {
|
|
46
|
+
return this._adapter;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
where<K extends FieldPath<T>>(
|
|
50
|
+
field: K,
|
|
51
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>,
|
|
52
|
+
value?: FilterValueFor<PathValue<T, K>>,
|
|
53
|
+
options?: CriteriaOptions
|
|
54
|
+
): this;
|
|
55
|
+
|
|
56
|
+
where<K extends FieldPath<T>>(
|
|
57
|
+
field: K,
|
|
58
|
+
operator: FilterOperator,
|
|
59
|
+
value?: FilterValueFor<PathValue<T, K>>,
|
|
60
|
+
options?: CriteriaOptions
|
|
61
|
+
): this {
|
|
62
|
+
this.validateOperator(operator, value);
|
|
63
|
+
|
|
64
|
+
this._filters.push({
|
|
65
|
+
field: this.resolveFieldPath(field),
|
|
66
|
+
operator,
|
|
67
|
+
value,
|
|
68
|
+
options,
|
|
69
|
+
});
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
whereEquals<K extends FieldPath<T>>(field: K, value: PathValue<T, K>): this {
|
|
74
|
+
return this.where(
|
|
75
|
+
field,
|
|
76
|
+
"equals" as OperatorsForType<PathValue<T, K>>,
|
|
77
|
+
value
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
whereContains<K extends FieldPath<T>>(
|
|
82
|
+
field: K,
|
|
83
|
+
value: PathValue<T, K>
|
|
84
|
+
): this {
|
|
85
|
+
return this.where(
|
|
86
|
+
field,
|
|
87
|
+
"contains" as OperatorsForType<PathValue<T, K>>,
|
|
88
|
+
value
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
whereIn<K extends FieldPath<T>>(field: K, values: PathValue<T, K>[]): this {
|
|
93
|
+
return this.where(field, "in" as OperatorsForType<PathValue<T, K>>, values);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
whereBetween<K extends FieldPath<T>>(
|
|
97
|
+
field: K,
|
|
98
|
+
min: PathValue<T, K>,
|
|
99
|
+
max: PathValue<T, K>
|
|
100
|
+
): this {
|
|
101
|
+
return this.where(
|
|
102
|
+
field,
|
|
103
|
+
"between" as OperatorsForType<PathValue<T, K>>,
|
|
104
|
+
[min, max] as [PathValue<T, K>, PathValue<T, K>]
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
whereNull<K extends FieldPath<T>>(field: K): this {
|
|
109
|
+
return this.where(field, "isNull" as OperatorsForType<PathValue<T, K>>);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
whereNotNull<K extends FieldPath<T>>(field: K): this {
|
|
113
|
+
return this.where(field, "isNotNull" as OperatorsForType<PathValue<T, K>>);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
whereSome<K extends FieldPath<T>>(
|
|
117
|
+
field: K,
|
|
118
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>,
|
|
119
|
+
value?: FilterValueFor<PathValue<T, K>>
|
|
120
|
+
): this {
|
|
121
|
+
return this.where(field, operator, value, { quantifier: "some" });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
whereEvery<K extends FieldPath<T>>(
|
|
125
|
+
field: K,
|
|
126
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>,
|
|
127
|
+
value?: FilterValueFor<PathValue<T, K>>
|
|
128
|
+
): this {
|
|
129
|
+
return this.where(field, operator, value, { quantifier: "every" });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
whereNone<K extends FieldPath<T>>(
|
|
133
|
+
field: K,
|
|
134
|
+
operator: OperatorsForType<NonNullable<PathValue<T, K>>>,
|
|
135
|
+
value?: FilterValueFor<PathValue<T, K>>
|
|
136
|
+
): this {
|
|
137
|
+
return this.where(field, operator, value, { quantifier: "none" });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
orderBy<K extends FieldPath<T>>(
|
|
141
|
+
field: K,
|
|
142
|
+
direction: OrderDirection = "asc"
|
|
143
|
+
): this {
|
|
144
|
+
this._orders.push({
|
|
145
|
+
field: this.resolveFieldPath(field),
|
|
146
|
+
direction,
|
|
147
|
+
});
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
orderByAsc<K extends FieldPath<T>>(field: K): this {
|
|
152
|
+
return this.orderBy(field, "asc");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
orderByDesc<K extends FieldPath<T>>(field: K): this {
|
|
156
|
+
return this.orderBy(field, "desc");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
search(value: string): this {
|
|
160
|
+
this._search = value;
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
hasSearch(): boolean {
|
|
165
|
+
return !!this._search;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getSearch(): Search | undefined {
|
|
169
|
+
return this._search;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
paginate(page: number, limit: number): this {
|
|
173
|
+
if (page < 1) page = 1;
|
|
174
|
+
if (limit < 1) limit = 10;
|
|
175
|
+
|
|
176
|
+
this._pagination = {
|
|
177
|
+
page,
|
|
178
|
+
limit,
|
|
179
|
+
offset: (page - 1) * limit,
|
|
180
|
+
};
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
limit(limit: number): this {
|
|
185
|
+
return this.paginate(1, limit);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getFilters(): Filter[] {
|
|
189
|
+
return this._filters.map((filter) => ({
|
|
190
|
+
field: this.resolveFieldPath(filter.field),
|
|
191
|
+
operator: filter.operator,
|
|
192
|
+
value: filter.value,
|
|
193
|
+
options: filter.options,
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getOrders(): Order[] {
|
|
198
|
+
return this._orders.map((order) => ({
|
|
199
|
+
field: this.resolveFieldPath(order.field as FieldPath<T>),
|
|
200
|
+
direction: order.direction,
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
getPagination(): Pagination {
|
|
205
|
+
return this._pagination;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
hasFilters(): boolean {
|
|
209
|
+
return this._filters.length > 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
hasOrders(): boolean {
|
|
213
|
+
return this._orders.length > 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
hasPagination(): boolean {
|
|
217
|
+
return this._pagination !== undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
clone(): Criteria<T> {
|
|
221
|
+
const cloned = Criteria.create<T>();
|
|
222
|
+
cloned._filters = [
|
|
223
|
+
...this._filters.map((filter) => ({
|
|
224
|
+
field: this.resolveFieldPath(filter.field),
|
|
225
|
+
operator: filter.operator,
|
|
226
|
+
value: filter.value,
|
|
227
|
+
options: filter.options,
|
|
228
|
+
})),
|
|
229
|
+
];
|
|
230
|
+
cloned._orders = [
|
|
231
|
+
...this._orders.map((order) => ({
|
|
232
|
+
field: this.resolveFieldPath(order.field as FieldPath<T>),
|
|
233
|
+
direction: order.direction,
|
|
234
|
+
})),
|
|
235
|
+
];
|
|
236
|
+
cloned._pagination = { ...this._pagination };
|
|
237
|
+
cloned._search = this._search;
|
|
238
|
+
|
|
239
|
+
if (this._adapter) {
|
|
240
|
+
cloned.useAdapter(this._adapter);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return cloned;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
toJSON() {
|
|
247
|
+
return {
|
|
248
|
+
filters: this._filters.map((filter) => ({
|
|
249
|
+
field: this.resolveFieldPath(filter.field),
|
|
250
|
+
operator: filter.operator,
|
|
251
|
+
value: filter.value,
|
|
252
|
+
options: filter.options,
|
|
253
|
+
})),
|
|
254
|
+
orders: this._orders.map((order) => ({
|
|
255
|
+
field: this.resolveFieldPath(order.field as FieldPath<T>),
|
|
256
|
+
direction: order.direction,
|
|
257
|
+
})),
|
|
258
|
+
pagination: this._pagination,
|
|
259
|
+
search: this._search,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
static fromObject<T>(
|
|
264
|
+
obj: {
|
|
265
|
+
filters?: TypedFilter<T>[];
|
|
266
|
+
orders?: TypedOrder<T>[];
|
|
267
|
+
pagination?: Pagination;
|
|
268
|
+
search?: Search;
|
|
269
|
+
},
|
|
270
|
+
adapter?: CriteriaAdapter<any, any>
|
|
271
|
+
): Criteria<T> {
|
|
272
|
+
const criteria = Criteria.create<T>();
|
|
273
|
+
|
|
274
|
+
if (adapter) {
|
|
275
|
+
criteria.useAdapter(adapter);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (obj.filters) {
|
|
279
|
+
for (const filter of obj.filters) {
|
|
280
|
+
filter.field = criteria.resolveFieldPath(filter.field);
|
|
281
|
+
criteria.validateOperator(filter.operator, filter.value);
|
|
282
|
+
}
|
|
283
|
+
criteria._filters = [...obj.filters];
|
|
284
|
+
}
|
|
285
|
+
if (obj.orders)
|
|
286
|
+
criteria._orders = [
|
|
287
|
+
...obj.orders.map((order) => ({
|
|
288
|
+
field: criteria.resolveFieldPath(order.field as FieldPath<T>),
|
|
289
|
+
direction: order.direction,
|
|
290
|
+
})),
|
|
291
|
+
];
|
|
292
|
+
if (obj.pagination) criteria._pagination = { ...obj.pagination };
|
|
293
|
+
if (obj.search) criteria._search = obj.search;
|
|
294
|
+
|
|
295
|
+
return criteria;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
protected resolveFieldPath(field: FieldPath<T>): FieldPath<T> {
|
|
299
|
+
if (!this?._adapter) return field;
|
|
300
|
+
|
|
301
|
+
if (this._adapter[field]) {
|
|
302
|
+
return this._adapter[field] as FieldPath<T>;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const parts = field.split(".");
|
|
306
|
+
for (let i = parts.length; i > 0; i--) {
|
|
307
|
+
const prefix = parts.slice(0, i).join(".");
|
|
308
|
+
if (this._adapter[prefix]) {
|
|
309
|
+
const rest = parts.slice(i).join(".");
|
|
310
|
+
return rest
|
|
311
|
+
? (`${this._adapter[prefix]}.${rest}` as FieldPath<T>)
|
|
312
|
+
: (this._adapter[prefix] as FieldPath<T>);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return field;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
static fromQueryParams<T = any>(
|
|
320
|
+
query: QueryParamsObject | undefined,
|
|
321
|
+
adapter?: CriteriaAdapter<any, any>
|
|
322
|
+
): Criteria<T> {
|
|
323
|
+
if (!query) return Criteria.create<T>();
|
|
324
|
+
|
|
325
|
+
const criteria = Criteria.create<T>();
|
|
326
|
+
|
|
327
|
+
if (adapter) {
|
|
328
|
+
criteria.useAdapter(adapter);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
for (const [key, value] of Object.entries(query)) {
|
|
332
|
+
if (key === "pagination") {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (key === "filters") {
|
|
337
|
+
const filters: Record<string, any> = criteria.parseFilterValue(value);
|
|
338
|
+
|
|
339
|
+
for (let [filterKey, filterValue] of Object.entries(filters)) {
|
|
340
|
+
const [field, operatorWithQuantifier] = filterKey.split(":");
|
|
341
|
+
|
|
342
|
+
if (!operatorWithQuantifier || !field) continue;
|
|
343
|
+
|
|
344
|
+
const [operatorRaw, quantifierRaw] =
|
|
345
|
+
operatorWithQuantifier.split("@");
|
|
346
|
+
const operator = isOperator(operatorRaw) ? operatorRaw : null;
|
|
347
|
+
if (!operator) {
|
|
348
|
+
throw new InvalidCriteriaError(
|
|
349
|
+
`Invalid filter operator`,
|
|
350
|
+
operatorRaw
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const validQuantifiers = ["some", "every", "none"];
|
|
355
|
+
const quantifier =
|
|
356
|
+
quantifierRaw && validQuantifiers.includes(quantifierRaw)
|
|
357
|
+
? (quantifierRaw as CriteriaOptions["quantifier"])
|
|
358
|
+
: undefined;
|
|
359
|
+
|
|
360
|
+
if (quantifierRaw && !quantifier) {
|
|
361
|
+
throw new InvalidCriteriaError(
|
|
362
|
+
`Invalid quantifier. Valid values: ${validQuantifiers.join(
|
|
363
|
+
", "
|
|
364
|
+
)}`,
|
|
365
|
+
quantifierRaw
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const options: CriteriaOptions | undefined = quantifier
|
|
370
|
+
? { quantifier }
|
|
371
|
+
: undefined;
|
|
372
|
+
|
|
373
|
+
let parsedValue: any = filterValue;
|
|
374
|
+
|
|
375
|
+
const resolvedField = criteria.resolveFieldPath(
|
|
376
|
+
field as FieldPath<T>
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
if (operator === "between") {
|
|
380
|
+
parsedValue = criteria
|
|
381
|
+
.parseFilterValue(filterValue)
|
|
382
|
+
.map((v: any) => {
|
|
383
|
+
if (typeof v === "string") {
|
|
384
|
+
return parseQueryValue(v.trim());
|
|
385
|
+
}
|
|
386
|
+
return parseQueryValue(v);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
if (parsedValue.length === 2) {
|
|
390
|
+
criteria.where(
|
|
391
|
+
resolvedField,
|
|
392
|
+
"between" as OperatorsForType<PathValue<T, FieldPath<T>>>,
|
|
393
|
+
[parsedValue[0], parsedValue[1]] as [
|
|
394
|
+
PathValue<T, FieldPath<T>>,
|
|
395
|
+
PathValue<T, FieldPath<T>>,
|
|
396
|
+
],
|
|
397
|
+
options
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (operator === "in" || operator === "notIn") {
|
|
404
|
+
parsedValue = criteria
|
|
405
|
+
.parseFilterValue(filterValue)
|
|
406
|
+
.map(parseQueryValue);
|
|
407
|
+
|
|
408
|
+
criteria.where(
|
|
409
|
+
field as any,
|
|
410
|
+
operator as OperatorsForType<PathValue<T, FieldPath<T>>>,
|
|
411
|
+
parsedValue,
|
|
412
|
+
options
|
|
413
|
+
);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const parsedFinalValue = parseQueryValue(filterValue);
|
|
418
|
+
|
|
419
|
+
criteria.validateOperator(operator, parsedFinalValue);
|
|
420
|
+
|
|
421
|
+
criteria.where(
|
|
422
|
+
field as FieldPath<T>,
|
|
423
|
+
operator as OperatorsForType<PathValue<T, FieldPath<T>>>,
|
|
424
|
+
parsedFinalValue,
|
|
425
|
+
options
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function parsePagination<T>(pagination: T | string) {
|
|
432
|
+
if (typeof pagination === "string") {
|
|
433
|
+
try {
|
|
434
|
+
return JSON.parse(pagination) as T;
|
|
435
|
+
} catch {
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return pagination;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const pagination = parsePagination(query.pagination);
|
|
443
|
+
|
|
444
|
+
const page = pagination?.page;
|
|
445
|
+
const limit = pagination?.limit;
|
|
446
|
+
|
|
447
|
+
if (page && limit) {
|
|
448
|
+
criteria.paginate(Number(page), Number(limit));
|
|
449
|
+
} else if (limit) {
|
|
450
|
+
criteria.paginate(1, Number(limit));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 1. orderBy=["field:asc","field2:desc"]
|
|
454
|
+
if (query.orderBy) {
|
|
455
|
+
const orderByValue = query.orderBy;
|
|
456
|
+
|
|
457
|
+
if (Array.isArray(orderByValue)) {
|
|
458
|
+
orderByValue.forEach((item: string) => {
|
|
459
|
+
const [field, direction] = item.split(":");
|
|
460
|
+
criteria.orderBy(
|
|
461
|
+
field as FieldPath<T>,
|
|
462
|
+
(direction as OrderDirection) || "asc"
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (query.search && typeof query.search === "string") {
|
|
469
|
+
criteria.search(query.search);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return criteria;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
toQueryObject(): QueryParamsObject {
|
|
476
|
+
const obj: QueryParamsObject = {};
|
|
477
|
+
const json = this.toJSON();
|
|
478
|
+
|
|
479
|
+
if (json.filters && json.filters.length > 0) {
|
|
480
|
+
const filtersObj: Record<string, unknown> = {};
|
|
481
|
+
for (const filter of json.filters) {
|
|
482
|
+
let filterKey = `${filter.field}:${filter.operator}`;
|
|
483
|
+
if (filter.options && filter.options.quantifier) {
|
|
484
|
+
filterKey += `@${filter.options.quantifier}`;
|
|
485
|
+
}
|
|
486
|
+
let value: string | undefined;
|
|
487
|
+
if (filter.value !== undefined) {
|
|
488
|
+
if (Array.isArray(filter.value)) {
|
|
489
|
+
value = JSON.stringify(filter.value);
|
|
490
|
+
} else {
|
|
491
|
+
if (filter.value instanceof Date) {
|
|
492
|
+
value = filter.value.toISOString();
|
|
493
|
+
} else {
|
|
494
|
+
value = String(filter.value);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
value = "";
|
|
499
|
+
}
|
|
500
|
+
filtersObj[filterKey] = value;
|
|
501
|
+
}
|
|
502
|
+
obj.filters = filtersObj;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (json.pagination) {
|
|
506
|
+
obj.pagination = json.pagination;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (json.orders && json.orders.length > 0) {
|
|
510
|
+
const sortValue = json.orders.map(
|
|
511
|
+
(order) => `${order.field}:${order.direction}`
|
|
512
|
+
);
|
|
513
|
+
obj.orderBy = sortValue;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (json.search) {
|
|
517
|
+
obj.search = json.search;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return obj;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
toQueryParams() {
|
|
524
|
+
const params = new URLSearchParams();
|
|
525
|
+
const object = this.toQueryObject();
|
|
526
|
+
|
|
527
|
+
if (object?.filters) {
|
|
528
|
+
params.set("filters", JSON.stringify(object.filters));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (object?.pagination) {
|
|
532
|
+
params.set("page", String(object.pagination.page));
|
|
533
|
+
params.set("limit", String(object.pagination.limit));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (object?.orderBy) {
|
|
537
|
+
params.set("orderBy", JSON.stringify(object.orderBy));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (object?.search) {
|
|
541
|
+
params.set("search", object.search);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return params;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private validateOperator(operator: FilterOperator, value: any): void {
|
|
548
|
+
const sanitizedValue = sanitizeFieldValue(value, operator);
|
|
549
|
+
|
|
550
|
+
if (
|
|
551
|
+
sanitizedValue !== undefined &&
|
|
552
|
+
!isValidOperatorForType(sanitizedValue, operator)
|
|
553
|
+
) {
|
|
554
|
+
const validOps = getValidOperatorsForType(sanitizedValue);
|
|
555
|
+
throw new InvalidCriteriaError(
|
|
556
|
+
`Operator "${operator}" is not valid for type "${typeof sanitizedValue}". Valid operators: ${validOps.join(
|
|
557
|
+
", "
|
|
558
|
+
)}`,
|
|
559
|
+
operator
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
private parseFilterValue(value: any) {
|
|
565
|
+
if (typeof value === "string") {
|
|
566
|
+
try {
|
|
567
|
+
return JSON.parse(value);
|
|
568
|
+
} catch {
|
|
569
|
+
throw new InvalidCriteriaError(`Invalid filter value`, value);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return parseQueryValue(value);
|
|
573
|
+
}
|
|
574
|
+
}
|