@woltz/rich-domain 1.2.4 → 1.3.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.
Files changed (72) hide show
  1. package/dist/aggregate-changes.d.ts +56 -14
  2. package/dist/aggregate-changes.d.ts.map +1 -1
  3. package/dist/aggregate-changes.js +102 -22
  4. package/dist/aggregate-changes.js.map +1 -1
  5. package/dist/base-entity.d.ts +1 -1
  6. package/dist/base-entity.d.ts.map +1 -1
  7. package/dist/base-entity.js +11 -8
  8. package/dist/base-entity.js.map +1 -1
  9. package/dist/change-tracker.d.ts +1 -0
  10. package/dist/change-tracker.d.ts.map +1 -1
  11. package/dist/change-tracker.js +41 -27
  12. package/dist/change-tracker.js.map +1 -1
  13. package/dist/criteria.d.ts +7 -15
  14. package/dist/criteria.d.ts.map +1 -1
  15. package/dist/criteria.js +99 -76
  16. package/dist/criteria.js.map +1 -1
  17. package/dist/entity-schema-registry.d.ts +133 -3
  18. package/dist/entity-schema-registry.d.ts.map +1 -1
  19. package/dist/entity-schema-registry.js +155 -4
  20. package/dist/entity-schema-registry.js.map +1 -1
  21. package/dist/paginated-result.d.ts +4 -4
  22. package/dist/paginated-result.d.ts.map +1 -1
  23. package/dist/paginated-result.js +6 -20
  24. package/dist/paginated-result.js.map +1 -1
  25. package/dist/types/change-tracker.d.ts +30 -0
  26. package/dist/types/change-tracker.d.ts.map +1 -1
  27. package/dist/types/criteria.d.ts +1 -4
  28. package/dist/types/criteria.d.ts.map +1 -1
  29. package/dist/types/domain.d.ts +2 -0
  30. package/dist/types/domain.d.ts.map +1 -1
  31. package/dist/types/utils.d.ts +2 -2
  32. package/dist/utils/helpers.d.ts +1 -0
  33. package/dist/utils/helpers.d.ts.map +1 -1
  34. package/dist/utils/helpers.js +23 -0
  35. package/dist/utils/helpers.js.map +1 -1
  36. package/dist/value-object.d.ts +1 -1
  37. package/dist/value-object.js +1 -1
  38. package/package.json +17 -3
  39. package/src/aggregate-changes.ts +133 -24
  40. package/src/base-entity.ts +13 -8
  41. package/src/change-tracker.ts +107 -38
  42. package/src/criteria.ts +151 -109
  43. package/src/entity-schema-registry.ts +253 -6
  44. package/src/paginated-result.ts +10 -30
  45. package/src/types/change-tracker.ts +31 -0
  46. package/src/types/criteria.ts +1 -4
  47. package/src/types/domain.ts +2 -0
  48. package/src/types/utils.ts +2 -2
  49. package/src/utils/helpers.ts +28 -0
  50. package/src/value-object.ts +1 -1
  51. package/.versionrc.json +0 -21
  52. package/CHANGELOG.md +0 -163
  53. package/tests/aggregate-changes.test.ts +0 -284
  54. package/tests/criteria.test.ts +0 -716
  55. package/tests/depth/deep-tracking.test.ts +0 -554
  56. package/tests/domain-events.test.ts +0 -431
  57. package/tests/entity-equality.test.ts +0 -464
  58. package/tests/entity-schema-registry.test.ts +0 -382
  59. package/tests/entity-validation.test.ts +0 -252
  60. package/tests/history-tracker.spec.ts +0 -439
  61. package/tests/id.test.ts +0 -338
  62. package/tests/load-test/data.json +0 -347211
  63. package/tests/load-test/entities.ts +0 -97
  64. package/tests/load-test/generate-data.ts +0 -81
  65. package/tests/load-test/lead-to-domain.mapper.ts +0 -24
  66. package/tests/load-test/load.test.ts +0 -38
  67. package/tests/repository.test.ts +0 -635
  68. package/tests/to-json.test.ts +0 -99
  69. package/tests/utils.ts +0 -290
  70. package/tests/value-object-validation.test.ts +0 -219
  71. package/tests/value-objects.test.ts +0 -80
  72. package/tsconfig.json +0 -9
@@ -431,9 +431,18 @@ export class ChangeTracker {
431
431
 
432
432
  const { depth, parentId, parentEntity } = arrayState.metadata;
433
433
 
434
+ const relationField = this.extractRelationField(path);
435
+
434
436
  for (const item of created) {
435
437
  const itemEntityName = this.getEntityName(item);
436
- changes.addCreate(itemEntityName, item, depth, parentId, parentEntity);
438
+ changes.addCreate(
439
+ itemEntityName,
440
+ item,
441
+ depth,
442
+ parentId,
443
+ parentEntity,
444
+ relationField
445
+ );
437
446
 
438
447
  this.markNestedItemsAsCreated(item, depth, changes);
439
448
  }
@@ -458,7 +467,15 @@ export class ChangeTracker {
458
467
  if (id || key) {
459
468
  const itemEntityName = this.getEntityName(item);
460
469
  const deleteId = id || key!;
461
- changes.addDelete(itemEntityName, deleteId, item, depth);
470
+ changes.addDelete(
471
+ itemEntityName,
472
+ deleteId,
473
+ item,
474
+ depth,
475
+ relationField,
476
+ parentId,
477
+ parentEntity
478
+ );
462
479
 
463
480
  this.markNestedItemsAsDeleted(item, depth, changes, rootTracker);
464
481
  }
@@ -476,37 +493,41 @@ export class ChangeTracker {
476
493
  ): void {
477
494
  if (!item || typeof item !== "object") return;
478
495
 
479
- const itemId = this.getEntityId(item);
480
- if (!itemId) return;
481
-
482
- const props = item.props || item;
496
+ const propsToScan = item.props || item;
497
+ const parentId = this.getEntityId(item);
498
+ const parentEntity = this.getEntityName(item);
483
499
 
484
- for (const [propName, value] of Object.entries(props)) {
500
+ for (const [propName, value] of Object.entries(propsToScan)) {
485
501
  if (propName === "id") continue;
486
502
 
487
503
  if (Array.isArray(value)) {
488
- for (const nestedItem of value) {
489
- if (this.isEntityOrVO(nestedItem)) {
490
- const nestedId = this.getEntityId(nestedItem);
491
- const nestedKey = this.getItemKey(nestedItem);
492
- if (nestedId || nestedKey) {
493
- const entityName = this.getEntityName(nestedItem);
494
- changes.addCreate(
495
- entityName,
496
- nestedItem,
497
- parentDepth + 1,
498
- itemId,
499
- this.getEntityName(item)
500
- );
501
-
502
- this.markNestedItemsAsCreated(
503
- nestedItem,
504
- parentDepth + 1,
505
- changes
506
- );
507
- }
504
+ const relationField = propName;
505
+
506
+ for (const child of value) {
507
+ if (this.isEntityOrVO(child)) {
508
+ const childEntityName = this.getEntityName(child);
509
+ changes.addCreate(
510
+ childEntityName,
511
+ child,
512
+ parentDepth + 1,
513
+ parentId,
514
+ parentEntity,
515
+ relationField
516
+ );
517
+ this.markNestedItemsAsCreated(child, parentDepth + 1, changes);
508
518
  }
509
519
  }
520
+ } else if (this.isEntityOrVO(value)) {
521
+ const childEntityName = this.getEntityName(value);
522
+ changes.addCreate(
523
+ childEntityName,
524
+ value,
525
+ parentDepth + 1,
526
+ parentId,
527
+ parentEntity,
528
+ propName
529
+ );
530
+ this.markNestedItemsAsCreated(value, parentDepth + 1, changes);
510
531
  }
511
532
  }
512
533
  }
@@ -526,8 +547,12 @@ export class ChangeTracker {
526
547
  const itemId = this.getEntityId(item);
527
548
  if (!itemId) return;
528
549
 
529
- for (const [, arrayState] of rootTracker.trackedArrays) {
550
+ for (const [path, arrayState] of rootTracker.trackedArrays) {
530
551
  if (arrayState.metadata.parentId === itemId) {
552
+ const relationField = this.extractRelationField(path);
553
+ const parentEntity = arrayState.metadata.parentEntity;
554
+ const parentId = arrayState.metadata.parentId;
555
+
531
556
  for (const nestedItem of arrayState.cloned) {
532
557
  const id =
533
558
  typeof nestedItem === "object" && nestedItem !== null
@@ -535,7 +560,15 @@ export class ChangeTracker {
535
560
  : undefined;
536
561
  if (id) {
537
562
  const entityName = arrayState.metadata.entityName;
538
- changes.addDelete(entityName, id, nestedItem, parentDepth + 1);
563
+ changes.addDelete(
564
+ entityName,
565
+ id,
566
+ nestedItem,
567
+ parentDepth + 1,
568
+ relationField,
569
+ parentEntity,
570
+ parentId
571
+ );
539
572
 
540
573
  this.markNestedJsonItemAsDeleted(
541
574
  id,
@@ -559,21 +592,28 @@ export class ChangeTracker {
559
592
  changes: AggregateChanges<any>,
560
593
  rootTracker: ChangeTracker
561
594
  ): void {
562
- for (const [, arrayState] of rootTracker.trackedArrays) {
595
+ for (const [path, arrayState] of rootTracker.trackedArrays) {
563
596
  if (arrayState.metadata.parentId === itemId) {
597
+ const relationField = this.extractRelationField(path);
598
+
564
599
  for (const nestedJsonItem of arrayState.cloned) {
565
600
  if (typeof nestedJsonItem !== "object" || nestedJsonItem === null)
566
601
  continue;
567
602
 
568
603
  const nestedId = nestedJsonItem.id;
569
604
  const entityName = arrayState.metadata.entityName;
605
+ const parentEntity = arrayState.metadata.parentEntity;
606
+ const parentId = arrayState.metadata.parentId;
570
607
 
571
608
  if (nestedId) {
572
609
  changes.addDelete(
573
610
  entityName,
574
611
  nestedId,
575
612
  nestedJsonItem,
576
- parentDepth + 1
613
+ parentDepth + 1,
614
+ relationField,
615
+ parentId,
616
+ parentEntity
577
617
  );
578
618
 
579
619
  this.markNestedJsonItemAsDeleted(
@@ -592,7 +632,10 @@ export class ChangeTracker {
592
632
  entityName,
593
633
  key,
594
634
  nestedJsonItem,
595
- parentDepth + 1
635
+ parentDepth + 1,
636
+ relationField,
637
+ parentId,
638
+ parentEntity
596
639
  );
597
640
  }
598
641
  }
@@ -668,6 +711,8 @@ export class ChangeTracker {
668
711
  const { entityName, depth, parentId, parentEntity } =
669
712
  trackedItem.metadata;
670
713
 
714
+ const relationField = this.extractRelationField(path);
715
+
671
716
  const state = this.detectEntityChangeState(originalValue, currentValue);
672
717
 
673
718
  switch (state) {
@@ -677,28 +722,46 @@ export class ChangeTracker {
677
722
  currentValue,
678
723
  depth,
679
724
  parentId,
680
- parentEntity
725
+ parentEntity,
726
+ relationField
681
727
  );
682
728
  break;
683
729
 
684
730
  case "deleted":
685
731
  const id = this.getEntityId(originalValue);
686
732
  if (id) {
687
- changes.addDelete(entityName, id, originalEntity, depth);
733
+ changes.addDelete(
734
+ entityName,
735
+ id,
736
+ originalEntity,
737
+ depth,
738
+ relationField,
739
+ parentId,
740
+ parentEntity
741
+ );
688
742
  }
689
743
  break;
690
744
 
691
745
  case "replaced":
692
746
  const oldId = this.getEntityId(originalValue);
693
747
  if (oldId) {
694
- changes.addDelete(entityName, oldId, originalEntity, depth);
748
+ changes.addDelete(
749
+ entityName,
750
+ oldId,
751
+ originalEntity,
752
+ depth,
753
+ relationField,
754
+ parentId,
755
+ parentEntity
756
+ );
695
757
  }
696
758
  changes.addCreate(
697
759
  entityName,
698
760
  currentValue,
699
761
  depth,
700
762
  parentId,
701
- parentEntity
763
+ parentEntity,
764
+ relationField
702
765
  );
703
766
  break;
704
767
 
@@ -894,6 +957,12 @@ export class ChangeTracker {
894
957
  return current;
895
958
  }
896
959
 
960
+ private extractRelationField(path: string): string {
961
+ const withoutIndices = path.replace(/\[\d+\]/g, "");
962
+ const parts = withoutIndices.split(".");
963
+ return parts[parts.length - 1];
964
+ }
965
+
897
966
  private getItemKey(item: any): string | undefined {
898
967
  const id = this.getEntityId(item);
899
968
  if (id) return id;
@@ -998,8 +1067,8 @@ export class ChangeTracker {
998
1067
  return obj.value;
999
1068
  }
1000
1069
 
1001
- if (typeof obj.toJson === "function") {
1002
- return obj.toJson();
1070
+ if (typeof obj.toJSON === "function") {
1071
+ return obj.toJSON();
1003
1072
  }
1004
1073
 
1005
1074
  if (Array.isArray(obj)) {
package/src/criteria.ts CHANGED
@@ -27,7 +27,7 @@ export class Criteria<T = any> {
27
27
  private _filters: Filter<FieldPath<T>, any>[] = [];
28
28
  private _orders: Order[] = [];
29
29
  private _pagination: Pagination = { page: 1, limit: 20, offset: 0 };
30
- private _search?: Search<T>;
30
+ private _search?: Search;
31
31
  private _adapter?: CriteriaAdapter<any, any>;
32
32
 
33
33
  private constructor() {}
@@ -155,11 +155,8 @@ export class Criteria<T = any> {
155
155
  return this.orderBy(field, "desc");
156
156
  }
157
157
 
158
- search<K extends FieldPath<T>>(fields: K[], value: string): this {
159
- this._search = {
160
- fields: fields.map(this.resolveFieldPath),
161
- value,
162
- };
158
+ search(value: string): this {
159
+ this._search = value;
163
160
  return this;
164
161
  }
165
162
 
@@ -167,13 +164,8 @@ export class Criteria<T = any> {
167
164
  return !!this._search;
168
165
  }
169
166
 
170
- getSearch() {
171
- return this._search
172
- ? {
173
- fields: this._search.fields.map(this.resolveFieldPath),
174
- value: this._search.value,
175
- }
176
- : undefined;
167
+ getSearch(): Search | undefined {
168
+ return this._search;
177
169
  }
178
170
 
179
171
  paginate(page: number, limit: number): this {
@@ -241,12 +233,7 @@ export class Criteria<T = any> {
241
233
  })),
242
234
  ];
243
235
  cloned._pagination = { ...this._pagination };
244
- cloned._search = this._search
245
- ? {
246
- fields: this._search.fields.map(this.resolveFieldPath),
247
- value: this._search.value,
248
- }
249
- : undefined;
236
+ cloned._search = this._search;
250
237
 
251
238
  if (this._adapter) {
252
239
  cloned.useAdapter(this._adapter);
@@ -268,12 +255,7 @@ export class Criteria<T = any> {
268
255
  direction: order.direction,
269
256
  })),
270
257
  pagination: this._pagination,
271
- search: this._search
272
- ? {
273
- fields: this._search.fields.map(this.resolveFieldPath),
274
- value: this._search.value,
275
- }
276
- : undefined,
258
+ search: this._search,
277
259
  };
278
260
  }
279
261
 
@@ -282,7 +264,7 @@ export class Criteria<T = any> {
282
264
  filters?: TypedFilter<T>[];
283
265
  orders?: TypedOrder<T>[];
284
266
  pagination?: Pagination;
285
- search?: { fields: FieldPath<T>[]; value: string };
267
+ search?: Search;
286
268
  },
287
269
  adapter?: CriteriaAdapter<any, any>
288
270
  ): Criteria<T> {
@@ -307,11 +289,7 @@ export class Criteria<T = any> {
307
289
  })),
308
290
  ];
309
291
  if (obj.pagination) criteria._pagination = { ...obj.pagination };
310
- if (obj.search)
311
- criteria._search = {
312
- ...obj.search,
313
- fields: obj.search.fields.map(criteria.resolveFieldPath),
314
- };
292
+ if (obj.search) criteria._search = obj.search;
315
293
 
316
294
  return criteria;
317
295
  }
@@ -337,10 +315,12 @@ export class Criteria<T = any> {
337
315
  return field;
338
316
  }
339
317
 
340
- static fromQueryParams<T>(
341
- query: Record<string, any>,
318
+ static fromQueryParams<T = any>(
319
+ query: Record<string, any> | undefined,
342
320
  adapter?: CriteriaAdapter<any, any>
343
321
  ): Criteria<T> {
322
+ if (!query) return Criteria.create<T>();
323
+
344
324
  const criteria = Criteria.create<T>();
345
325
 
346
326
  if (adapter) {
@@ -354,80 +334,95 @@ export class Criteria<T = any> {
354
334
  if (key === "limit") {
355
335
  continue;
356
336
  }
357
- if (key === "sort") {
358
- continue;
359
- }
360
337
 
361
- const [field, operatorWithQuantifier] = key.split(":");
338
+ if (key === "filters") {
339
+ const filters: Record<string, any> = criteria.parseFilterValue(value);
362
340
 
363
- if (!operatorWithQuantifier || !field) continue;
341
+ for (let [filterKey, filterValue] of Object.entries(filters)) {
342
+ const [field, operatorWithQuantifier] = filterKey.split(":");
364
343
 
365
- const [operatorRaw, quantifierRaw] = operatorWithQuantifier.split("@");
366
- const operator = isOperator(operatorRaw) ? operatorRaw : null;
367
- if (!operator) {
368
- throw new InvalidCriteriaError(`Invalid filter operator`, operatorRaw);
369
- }
344
+ if (!operatorWithQuantifier || !field) continue;
370
345
 
371
- const validQuantifiers = ["some", "every", "none"];
372
- const quantifier =
373
- quantifierRaw && validQuantifiers.includes(quantifierRaw)
374
- ? (quantifierRaw as CriteriaOptions["quantifier"])
375
- : undefined;
376
-
377
- if (quantifierRaw && !quantifier) {
378
- throw new InvalidCriteriaError(
379
- `Invalid quantifier. Valid values: ${validQuantifiers.join(", ")}`,
380
- quantifierRaw
381
- );
382
- }
346
+ const [operatorRaw, quantifierRaw] =
347
+ operatorWithQuantifier.split("@");
348
+ const operator = isOperator(operatorRaw) ? operatorRaw : null;
349
+ if (!operator) {
350
+ throw new InvalidCriteriaError(
351
+ `Invalid filter operator`,
352
+ operatorRaw
353
+ );
354
+ }
383
355
 
384
- const options: CriteriaOptions | undefined = quantifier
385
- ? { quantifier }
386
- : undefined;
356
+ const validQuantifiers = ["some", "every", "none"];
357
+ const quantifier =
358
+ quantifierRaw && validQuantifiers.includes(quantifierRaw)
359
+ ? (quantifierRaw as CriteriaOptions["quantifier"])
360
+ : undefined;
361
+
362
+ if (quantifierRaw && !quantifier) {
363
+ throw new InvalidCriteriaError(
364
+ `Invalid quantifier. Valid values: ${validQuantifiers.join(
365
+ ", "
366
+ )}`,
367
+ quantifierRaw
368
+ );
369
+ }
387
370
 
388
- let parsedValue: any = value;
371
+ const options: CriteriaOptions | undefined = quantifier
372
+ ? { quantifier }
373
+ : undefined;
389
374
 
390
- const resolvedField = criteria.resolveFieldPath(field as FieldPath<T>);
375
+ let parsedValue: any = filterValue;
391
376
 
392
- if (operator === "between") {
393
- parsedValue = value
394
- .split(",")
395
- .map((v: any) => parseQueryValue(v.trim()));
396
- if (parsedValue.length === 2) {
397
- criteria.where(
398
- resolvedField,
399
- "between" as OperatorsForType<PathValue<T, FieldPath<T>>>,
400
- [parsedValue[0], parsedValue[1]] as [
401
- PathValue<T, FieldPath<T>>,
402
- PathValue<T, FieldPath<T>>
403
- ],
404
- options
377
+ const resolvedField = criteria.resolveFieldPath(
378
+ field as FieldPath<T>
405
379
  );
406
- }
407
- continue;
408
- }
409
380
 
410
- if (operator === "in" || operator === "notIn") {
411
- parsedValue = value.split(",").map(parseQueryValue);
412
- criteria.where(
413
- field as any,
414
- operator as OperatorsForType<PathValue<T, FieldPath<T>>>,
415
- parsedValue,
416
- options
417
- );
418
- continue;
419
- }
381
+ if (operator === "between") {
382
+ parsedValue = criteria
383
+ .parseFilterValue(filterValue)
384
+ .map((v: any) => parseQueryValue(v.trim()));
385
+
386
+ if (parsedValue.length === 2) {
387
+ criteria.where(
388
+ resolvedField,
389
+ "between" as OperatorsForType<PathValue<T, FieldPath<T>>>,
390
+ [parsedValue[0], parsedValue[1]] as [
391
+ PathValue<T, FieldPath<T>>,
392
+ PathValue<T, FieldPath<T>>
393
+ ],
394
+ options
395
+ );
396
+ }
397
+ continue;
398
+ }
420
399
 
421
- const parsedFinalValue = parseQueryValue(value);
400
+ if (operator === "in" || operator === "notIn") {
401
+ parsedValue = criteria
402
+ .parseFilterValue(filterValue)
403
+ .map(parseQueryValue);
404
+
405
+ criteria.where(
406
+ field as any,
407
+ operator as OperatorsForType<PathValue<T, FieldPath<T>>>,
408
+ parsedValue,
409
+ options
410
+ );
411
+ continue;
412
+ }
422
413
 
423
- criteria.validateOperator(operator, parsedFinalValue);
414
+ const parsedFinalValue = parseQueryValue(filterValue);
424
415
 
425
- criteria.where(
426
- field as FieldPath<T>,
427
- operator as OperatorsForType<PathValue<T, FieldPath<T>>>,
428
- parsedFinalValue,
429
- options
430
- );
416
+ criteria.validateOperator(operator, parsedFinalValue);
417
+
418
+ criteria.where(
419
+ field as FieldPath<T>,
420
+ operator as OperatorsForType<PathValue<T, FieldPath<T>>>,
421
+ parsedFinalValue,
422
+ options
423
+ );
424
+ }
425
+ }
431
426
  }
432
427
 
433
428
  const page = query.page ? parseInt(query.page) : undefined;
@@ -437,24 +432,60 @@ export class Criteria<T = any> {
437
432
  criteria.paginate(page, limit);
438
433
  }
439
434
 
435
+ // 1. orderBy=["field:asc","field2:desc"]
440
436
  if (query.orderBy) {
441
- const sortParts = query.orderBy.split(",");
442
- sortParts.forEach((part: string) => {
443
- const [field, direction] = part.split(":");
444
- criteria.orderBy(
445
- field as FieldPath<T>,
446
- (direction as OrderDirection) || "asc"
447
- );
448
- });
437
+ const orderByValue = query.orderBy;
438
+
439
+ if (
440
+ typeof orderByValue === "string" &&
441
+ orderByValue.trim().startsWith("[")
442
+ ) {
443
+ try {
444
+ const orderArray = JSON.parse(orderByValue);
445
+ if (Array.isArray(orderArray)) {
446
+ orderArray.forEach((item: string) => {
447
+ const [field, direction] = item.split(":");
448
+ criteria.orderBy(
449
+ field as FieldPath<T>,
450
+ (direction as OrderDirection) || "asc"
451
+ );
452
+ });
453
+ }
454
+ } catch {
455
+ throw new InvalidCriteriaError(
456
+ "Invalid JSON array format for orderBy",
457
+ orderByValue
458
+ );
459
+ }
460
+ } else if (Array.isArray(orderByValue)) {
461
+ orderByValue.forEach((item: string) => {
462
+ const [field, direction] = item.split(":");
463
+ criteria.orderBy(
464
+ field as FieldPath<T>,
465
+ (direction as OrderDirection) || "asc"
466
+ );
467
+ });
468
+ }
469
+ // 2. orderBy="field:asc,field2:desc"
470
+ else if (typeof orderByValue === "string" && orderByValue.includes(":")) {
471
+ const sortParts = orderByValue.split(",");
472
+ sortParts.forEach((part: string) => {
473
+ const [field, direction] = part.split(":");
474
+ criteria.orderBy(
475
+ field as FieldPath<T>,
476
+ (direction as OrderDirection) || "asc"
477
+ );
478
+ });
479
+ }
480
+ // 3. orderBy="field" + orderDirection="asc"
481
+ else {
482
+ const direction = (query.orderDirection as OrderDirection) || "asc";
483
+ criteria.orderBy(orderByValue as FieldPath<T>, direction);
484
+ }
449
485
  }
450
486
 
451
- if (query.search && query.searchFields) {
452
- const fields = (query.searchFields as string)
453
- .split(",")
454
- .filter(Boolean) as FieldPath<T>[];
455
-
456
- const resolvedFields = fields.map(criteria.resolveFieldPath);
457
- criteria.search(resolvedFields, query.search as string);
487
+ if (query.search && typeof query.search === "string") {
488
+ criteria.search(query.search);
458
489
  }
459
490
 
460
491
  return criteria;
@@ -476,4 +507,15 @@ export class Criteria<T = any> {
476
507
  );
477
508
  }
478
509
  }
510
+
511
+ private parseFilterValue(value: any) {
512
+ if (typeof value === "string") {
513
+ try {
514
+ return JSON.parse(value);
515
+ } catch {
516
+ throw new InvalidCriteriaError(`Invalid filter value`, value);
517
+ }
518
+ }
519
+ return parseQueryValue(value);
520
+ }
479
521
  }