@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.
Files changed (95) hide show
  1. package/dist/cjs/core/aggregate-changes.d.ts +14 -0
  2. package/dist/cjs/core/aggregate-changes.d.ts.map +1 -1
  3. package/dist/cjs/core/aggregate-changes.js +18 -0
  4. package/dist/cjs/core/aggregate-changes.js.map +1 -1
  5. package/dist/cjs/core/base-entity.d.ts +2 -0
  6. package/dist/cjs/core/base-entity.d.ts.map +1 -1
  7. package/dist/cjs/core/base-entity.js +39 -41
  8. package/dist/cjs/core/base-entity.js.map +1 -1
  9. package/dist/cjs/core/change-tracker.d.ts +8 -0
  10. package/dist/cjs/core/change-tracker.d.ts.map +1 -1
  11. package/dist/cjs/core/change-tracker.js +36 -6
  12. package/dist/cjs/core/change-tracker.js.map +1 -1
  13. package/dist/cjs/core/value-object.d.ts.map +1 -1
  14. package/dist/cjs/core/value-object.js +3 -5
  15. package/dist/cjs/core/value-object.js.map +1 -1
  16. package/dist/cjs/repository/entity-schema-registry.d.ts +56 -3
  17. package/dist/cjs/repository/entity-schema-registry.d.ts.map +1 -1
  18. package/dist/cjs/repository/entity-schema-registry.js +61 -6
  19. package/dist/cjs/repository/entity-schema-registry.js.map +1 -1
  20. package/dist/cjs/utils/helpers.d.ts +1 -0
  21. package/dist/cjs/utils/helpers.d.ts.map +1 -1
  22. package/dist/cjs/utils/helpers.js +4 -0
  23. package/dist/cjs/utils/helpers.js.map +1 -1
  24. package/dist/esm/core/aggregate-changes.d.ts +14 -0
  25. package/dist/esm/core/aggregate-changes.d.ts.map +1 -1
  26. package/dist/esm/core/aggregate-changes.js +18 -0
  27. package/dist/esm/core/aggregate-changes.js.map +1 -1
  28. package/dist/esm/core/base-entity.d.ts +2 -0
  29. package/dist/esm/core/base-entity.d.ts.map +1 -1
  30. package/dist/esm/core/base-entity.js +37 -39
  31. package/dist/esm/core/base-entity.js.map +1 -1
  32. package/dist/esm/core/change-tracker.d.ts +8 -0
  33. package/dist/esm/core/change-tracker.d.ts.map +1 -1
  34. package/dist/esm/core/change-tracker.js +36 -6
  35. package/dist/esm/core/change-tracker.js.map +1 -1
  36. package/dist/esm/core/value-object.d.ts.map +1 -1
  37. package/dist/esm/core/value-object.js +1 -3
  38. package/dist/esm/core/value-object.js.map +1 -1
  39. package/dist/esm/repository/entity-schema-registry.d.ts +56 -3
  40. package/dist/esm/repository/entity-schema-registry.d.ts.map +1 -1
  41. package/dist/esm/repository/entity-schema-registry.js +61 -6
  42. package/dist/esm/repository/entity-schema-registry.js.map +1 -1
  43. package/dist/esm/utils/helpers.d.ts +1 -0
  44. package/dist/esm/utils/helpers.d.ts.map +1 -1
  45. package/dist/esm/utils/helpers.js +3 -0
  46. package/dist/esm/utils/helpers.js.map +1 -1
  47. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  48. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  49. package/dist/tsconfig.types.tsbuildinfo +1 -1
  50. package/dist/types/core/aggregate-changes.d.ts +14 -0
  51. package/dist/types/core/aggregate-changes.d.ts.map +1 -1
  52. package/dist/types/core/base-entity.d.ts +2 -0
  53. package/dist/types/core/base-entity.d.ts.map +1 -1
  54. package/dist/types/core/change-tracker.d.ts +8 -0
  55. package/dist/types/core/change-tracker.d.ts.map +1 -1
  56. package/dist/types/core/value-object.d.ts.map +1 -1
  57. package/dist/types/repository/entity-schema-registry.d.ts +56 -3
  58. package/dist/types/repository/entity-schema-registry.d.ts.map +1 -1
  59. package/dist/types/utils/helpers.d.ts +1 -0
  60. package/dist/types/utils/helpers.d.ts.map +1 -1
  61. package/package.json +68 -67
  62. package/src/constants.ts +82 -0
  63. package/src/core/aggregate-changes.ts +466 -0
  64. package/src/core/base-aggregate.ts +76 -0
  65. package/src/core/base-entity.ts +552 -0
  66. package/src/core/change-tracker.ts +1327 -0
  67. package/src/core/domain-event.ts +41 -0
  68. package/src/core/entity-changes.ts +146 -0
  69. package/src/core/entity.ts +13 -0
  70. package/src/core/id.ts +124 -0
  71. package/src/core/index.ts +9 -0
  72. package/src/core/value-object.ts +179 -0
  73. package/src/criteria.ts +574 -0
  74. package/src/exceptions.ts +549 -0
  75. package/src/index.ts +74 -0
  76. package/src/repository/base-repository.ts +81 -0
  77. package/src/repository/entity-schema-registry.ts +620 -0
  78. package/src/repository/index.ts +5 -0
  79. package/src/repository/mapper.ts +7 -0
  80. package/src/repository/paginated-result.ts +251 -0
  81. package/src/repository/unit-of-work.ts +76 -0
  82. package/src/types/change-tracker.ts +268 -0
  83. package/src/types/criteria.ts +197 -0
  84. package/src/types/domain-event.ts +29 -0
  85. package/src/types/domain.ts +41 -0
  86. package/src/types/event-bus.ts +17 -0
  87. package/src/types/index.ts +9 -0
  88. package/src/types/outbox-store.ts +97 -0
  89. package/src/types/standard-schema.ts +19 -0
  90. package/src/types/unit-of-work.ts +46 -0
  91. package/src/types/utils.ts +24 -0
  92. package/src/utils/criteria-operator-validation.ts +209 -0
  93. package/src/utils/crypto.ts +31 -0
  94. package/src/utils/helpers.ts +50 -0
  95. package/src/validation-error.ts +219 -0
@@ -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
+ }