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