@ls-stack/utils 3.49.0 → 3.50.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.
@@ -63,6 +63,18 @@ var match = {
63
63
  isLessThanOrEqual: (value) => createComparison(["numIsLessThanOrEqual", value]),
64
64
  isInRange: (value) => createComparison(["numIsInRange", value])
65
65
  },
66
+ array: {
67
+ contains: (elements) => createComparison(["arrayContains", elements]),
68
+ containsInOrder: (elements) => createComparison(["arrayContainsInOrder", elements]),
69
+ startsWith: (elements) => createComparison(["arrayStartsWith", elements]),
70
+ endsWith: (elements) => createComparison(["arrayEndsWith", elements]),
71
+ length: (n) => createComparison(["arrayLength", n]),
72
+ minLength: (n) => createComparison(["arrayMinLength", n]),
73
+ maxLength: (n) => createComparison(["arrayMaxLength", n]),
74
+ includes: (element) => createComparison(["arrayIncludes", element]),
75
+ every: (matcher) => createComparison(["arrayEvery", matcher["~sc"]]),
76
+ some: (matcher) => createComparison(["arraySome", matcher["~sc"]])
77
+ },
66
78
  jsonString: {
67
79
  hasPartial: (value) => createComparison(["jsonStringHasPartial", value])
68
80
  },
@@ -112,6 +124,18 @@ var match = {
112
124
  isLessThanOrEqual: (value) => createComparison(["not", ["numIsLessThanOrEqual", value]]),
113
125
  isInRange: (value) => createComparison(["not", ["numIsInRange", value]])
114
126
  },
127
+ array: {
128
+ contains: (elements) => createComparison(["not", ["arrayContains", elements]]),
129
+ containsInOrder: (elements) => createComparison(["not", ["arrayContainsInOrder", elements]]),
130
+ startsWith: (elements) => createComparison(["not", ["arrayStartsWith", elements]]),
131
+ endsWith: (elements) => createComparison(["not", ["arrayEndsWith", elements]]),
132
+ length: (n) => createComparison(["not", ["arrayLength", n]]),
133
+ minLength: (n) => createComparison(["not", ["arrayMinLength", n]]),
134
+ maxLength: (n) => createComparison(["not", ["arrayMaxLength", n]]),
135
+ includes: (element) => createComparison(["not", ["arrayIncludes", element]]),
136
+ every: (matcher) => createComparison(["not", ["arrayEvery", matcher["~sc"]]]),
137
+ some: (matcher) => createComparison(["not", ["arraySome", matcher["~sc"]]])
138
+ },
115
139
  jsonString: {
116
140
  hasPartial: (value) => createComparison(["not", ["jsonStringHasPartial", value]])
117
141
  },
@@ -396,6 +420,248 @@ function executeComparison(target, comparison, context) {
396
420
  return checkNoExtraDefinedKeys(target, value, context, false);
397
421
  case "deepNoExtraDefinedKeys":
398
422
  return checkNoExtraDefinedKeys(target, value, context, true);
423
+ case "arrayContains": {
424
+ if (!Array.isArray(target)) {
425
+ addError(context, {
426
+ message: "Expected array",
427
+ received: target
428
+ });
429
+ return false;
430
+ }
431
+ for (const element of value) {
432
+ let found = false;
433
+ for (const targetElement of target) {
434
+ const tempContext = {
435
+ errors: [],
436
+ path: context.path
437
+ };
438
+ if (partialEqualInternal(targetElement, element, tempContext)) {
439
+ found = true;
440
+ break;
441
+ }
442
+ }
443
+ if (!found) {
444
+ addError(context, {
445
+ message: "Array does not contain expected element",
446
+ received: target,
447
+ expected: element
448
+ });
449
+ return false;
450
+ }
451
+ }
452
+ return true;
453
+ }
454
+ case "arrayContainsInOrder": {
455
+ if (!Array.isArray(target)) {
456
+ addError(context, {
457
+ message: "Expected array",
458
+ received: target
459
+ });
460
+ return false;
461
+ }
462
+ let targetIndex = 0;
463
+ for (const element of value) {
464
+ let found = false;
465
+ for (let i = targetIndex; i < target.length; i++) {
466
+ const tempContext = {
467
+ errors: [],
468
+ path: context.path
469
+ };
470
+ if (partialEqualInternal(target[i], element, tempContext)) {
471
+ targetIndex = i + 1;
472
+ found = true;
473
+ break;
474
+ }
475
+ }
476
+ if (!found) {
477
+ addError(context, {
478
+ message: "Array does not contain expected elements in order",
479
+ received: target,
480
+ expected: value
481
+ });
482
+ return false;
483
+ }
484
+ }
485
+ return true;
486
+ }
487
+ case "arrayStartsWith": {
488
+ if (!Array.isArray(target)) {
489
+ addError(context, {
490
+ message: "Expected array",
491
+ received: target
492
+ });
493
+ return false;
494
+ }
495
+ if (target.length < value.length) {
496
+ addError(context, {
497
+ message: `Array too short: expected to start with ${value.length} elements, got ${target.length}`,
498
+ received: target,
499
+ expected: value
500
+ });
501
+ return false;
502
+ }
503
+ let allMatch = true;
504
+ for (let i = 0; i < value.length; i++) {
505
+ const oldPath = context.path;
506
+ context.path = [...oldPath, `[${i}]`];
507
+ const result = partialEqualInternal(target[i], value[i], context);
508
+ context.path = oldPath;
509
+ if (!result) allMatch = false;
510
+ }
511
+ return allMatch;
512
+ }
513
+ case "arrayEndsWith": {
514
+ if (!Array.isArray(target)) {
515
+ addError(context, {
516
+ message: "Expected array",
517
+ received: target
518
+ });
519
+ return false;
520
+ }
521
+ if (target.length < value.length) {
522
+ addError(context, {
523
+ message: `Array too short: expected to end with ${value.length} elements, got ${target.length}`,
524
+ received: target,
525
+ expected: value
526
+ });
527
+ return false;
528
+ }
529
+ let allMatch = true;
530
+ const startIndex = target.length - value.length;
531
+ for (let i = 0; i < value.length; i++) {
532
+ const oldPath = context.path;
533
+ context.path = [...oldPath, `[${startIndex + i}]`];
534
+ const result = partialEqualInternal(
535
+ target[startIndex + i],
536
+ value[i],
537
+ context
538
+ );
539
+ context.path = oldPath;
540
+ if (!result) allMatch = false;
541
+ }
542
+ return allMatch;
543
+ }
544
+ case "arrayLength": {
545
+ if (!Array.isArray(target)) {
546
+ addError(context, {
547
+ message: "Expected array",
548
+ received: target
549
+ });
550
+ return false;
551
+ }
552
+ if (target.length !== value) {
553
+ addError(context, {
554
+ message: `Expected array length ${value}, got ${target.length}`,
555
+ received: target
556
+ });
557
+ return false;
558
+ }
559
+ return true;
560
+ }
561
+ case "arrayMinLength": {
562
+ if (!Array.isArray(target)) {
563
+ addError(context, {
564
+ message: "Expected array",
565
+ received: target
566
+ });
567
+ return false;
568
+ }
569
+ if (target.length < value) {
570
+ addError(context, {
571
+ message: `Expected array with at least ${value} elements, got ${target.length}`,
572
+ received: target
573
+ });
574
+ return false;
575
+ }
576
+ return true;
577
+ }
578
+ case "arrayMaxLength": {
579
+ if (!Array.isArray(target)) {
580
+ addError(context, {
581
+ message: "Expected array",
582
+ received: target
583
+ });
584
+ return false;
585
+ }
586
+ if (target.length > value) {
587
+ addError(context, {
588
+ message: `Expected array with at most ${value} elements, got ${target.length}`,
589
+ received: target
590
+ });
591
+ return false;
592
+ }
593
+ return true;
594
+ }
595
+ case "arrayIncludes": {
596
+ if (!Array.isArray(target)) {
597
+ addError(context, {
598
+ message: "Expected array",
599
+ received: target
600
+ });
601
+ return false;
602
+ }
603
+ let found = false;
604
+ for (const targetElement of target) {
605
+ const tempContext = {
606
+ errors: [],
607
+ path: context.path
608
+ };
609
+ if (partialEqualInternal(targetElement, value, tempContext)) {
610
+ found = true;
611
+ break;
612
+ }
613
+ }
614
+ if (!found) {
615
+ addError(context, {
616
+ message: "Array does not include expected element",
617
+ received: target,
618
+ expected: value
619
+ });
620
+ return false;
621
+ }
622
+ return true;
623
+ }
624
+ case "arrayEvery": {
625
+ if (!Array.isArray(target)) {
626
+ addError(context, {
627
+ message: "Expected array",
628
+ received: target
629
+ });
630
+ return false;
631
+ }
632
+ let allMatch = true;
633
+ for (let i = 0; i < target.length; i++) {
634
+ const oldPath = context.path;
635
+ context.path = [...oldPath, `[${i}]`];
636
+ const result = executeComparison(target[i], value, context);
637
+ context.path = oldPath;
638
+ if (!result) allMatch = false;
639
+ }
640
+ return allMatch;
641
+ }
642
+ case "arraySome": {
643
+ if (!Array.isArray(target)) {
644
+ addError(context, {
645
+ message: "Expected array",
646
+ received: target
647
+ });
648
+ return false;
649
+ }
650
+ for (let i = 0; i < target.length; i++) {
651
+ const tempContext = {
652
+ errors: [],
653
+ path: [...context.path, `[${i}]`]
654
+ };
655
+ if (executeComparison(target[i], value, tempContext)) {
656
+ return true;
657
+ }
658
+ }
659
+ addError(context, {
660
+ message: "No array element matches the condition",
661
+ received: target
662
+ });
663
+ return false;
664
+ }
399
665
  default:
400
666
  throw exhaustiveCheck(type);
401
667
  }
@@ -452,9 +718,8 @@ function partialEqualInternal(target, sub, context) {
452
718
  }
453
719
  if (isComparison(sub) && sub["~sc"][0] === "keyNotBePresent") {
454
720
  addError(context, {
455
- message: "Key should not be present",
456
- received: target,
457
- expected: "key not present"
721
+ message: "This property should not be present",
722
+ received: target
458
723
  });
459
724
  return false;
460
725
  }
@@ -528,9 +793,9 @@ function partialEqualInternal(target, sub, context) {
528
793
  if (target instanceof Map && sub instanceof Map) {
529
794
  if (sub.size > target.size) {
530
795
  addError(context, {
531
- message: "Map too small",
532
- received: target,
533
- expected: sub
796
+ message: "Map has less entries than expected",
797
+ received: `${target.size} entries`,
798
+ expected: `${sub.size} entries`
534
799
  });
535
800
  return false;
536
801
  }
@@ -613,8 +878,7 @@ function partialEqualInternal(target, sub, context) {
613
878
  context.path = [...oldPath2, key];
614
879
  addError(context, {
615
880
  message: "Key should not be present",
616
- received: target[key],
617
- expected: "key not present"
881
+ received: target[key]
618
882
  });
619
883
  context.path = oldPath2;
620
884
  allMatch = false;
@@ -638,8 +902,8 @@ function partialEqualInternal(target, sub, context) {
638
902
  context.path = [...oldPath2, key];
639
903
  addError(context, {
640
904
  message: "Missing property",
641
- received: void 0,
642
- expected: sub[key]
905
+ expected: sub[key],
906
+ received: { objectWithKeys: Object.keys(target) }
643
907
  });
644
908
  context.path = oldPath2;
645
909
  allMatch = false;
@@ -680,7 +944,8 @@ function checkNoExtraKeys(target, partialShape, context, deep) {
680
944
  const oldPath = context.path;
681
945
  context.path = [...oldPath, key];
682
946
  addError(context, {
683
- message: `Extra key "${key}" not expected`
947
+ message: `Extra key "${key}" should not be present`,
948
+ received: target[key]
684
949
  });
685
950
  context.path = oldPath;
686
951
  return false;
@@ -721,7 +986,8 @@ function checkNoExtraDefinedKeys(target, partialShape, context, deep) {
721
986
  const oldPath = context.path;
722
987
  context.path = [...oldPath, key];
723
988
  addError(context, {
724
- message: `Extra defined key "${key}" not expected`
989
+ message: `Extra defined key "${key}" should not be present`,
990
+ received: target[key]
725
991
  });
726
992
  context.path = oldPath;
727
993
  return false;
@@ -3,7 +3,7 @@ import { Result } from 't-result';
3
3
  type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsWith', value: string] | [
4
4
  type: 'hasType',
5
5
  value: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'function'
6
- ] | [type: 'strContains', value: string] | [type: 'strMatchesRegex', value: RegExp] | [type: 'deepEqual', value: any] | [type: 'numIsGreaterThan', value: number] | [type: 'numIsGreaterThanOrEqual', value: number] | [type: 'numIsLessThan', value: number] | [type: 'numIsLessThanOrEqual', value: number] | [type: 'numIsInRange', value: [number, number]] | [type: 'jsonStringHasPartial', value: any] | [type: 'partialEqual', value: any] | [type: 'custom', value: (target: unknown) => boolean | {
6
+ ] | [type: 'strContains', value: string] | [type: 'strMatchesRegex', value: RegExp] | [type: 'deepEqual', value: any] | [type: 'numIsGreaterThan', value: number] | [type: 'numIsGreaterThanOrEqual', value: number] | [type: 'numIsLessThan', value: number] | [type: 'numIsLessThanOrEqual', value: number] | [type: 'numIsInRange', value: [number, number]] | [type: 'arrayContains', value: any[]] | [type: 'arrayContainsInOrder', value: any[]] | [type: 'arrayStartsWith', value: any[]] | [type: 'arrayEndsWith', value: any[]] | [type: 'arrayLength', value: number] | [type: 'arrayMinLength', value: number] | [type: 'arrayMaxLength', value: number] | [type: 'arrayIncludes', value: any] | [type: 'arrayEvery', value: ComparisonsType] | [type: 'arraySome', value: ComparisonsType] | [type: 'jsonStringHasPartial', value: any] | [type: 'partialEqual', value: any] | [type: 'custom', value: (target: unknown) => boolean | {
7
7
  error: string;
8
8
  }] | [type: 'isInstanceOf', value: new (...args: any[]) => any] | [type: 'keyNotBePresent', value: null] | [type: 'not', value: ComparisonsType] | [type: 'any', value: ComparisonsType[]] | [type: 'all', value: ComparisonsType[]] | [type: 'withNoExtraKeys', partialShape: any] | [type: 'withDeepNoExtraKeys', partialShape: any] | [type: 'noExtraDefinedKeys', partialShape: any] | [type: 'deepNoExtraDefinedKeys', partialShape: any];
9
9
  type Comparison = {
@@ -36,6 +36,18 @@ type BaseMatch = {
36
36
  isLessThanOrEqual: (value: number) => Comparison;
37
37
  isInRange: (value: [number, number]) => Comparison;
38
38
  };
39
+ array: {
40
+ contains: (elements: any[]) => Comparison;
41
+ containsInOrder: (elements: any[]) => Comparison;
42
+ startsWith: (elements: any[]) => Comparison;
43
+ endsWith: (elements: any[]) => Comparison;
44
+ length: (n: number) => Comparison;
45
+ minLength: (n: number) => Comparison;
46
+ maxLength: (n: number) => Comparison;
47
+ includes: (element: any) => Comparison;
48
+ every: (matcher: Comparison) => Comparison;
49
+ some: (matcher: Comparison) => Comparison;
50
+ };
39
51
  jsonString: {
40
52
  hasPartial: (value: any) => Comparison;
41
53
  };
@@ -66,7 +78,21 @@ type PartialError = {
66
78
  * @example
67
79
  * // Basic partial matching
68
80
  * partialEqual({ a: 1, b: 2 }, { a: 1 }); // true - sub is subset of target
69
- * partialEqual([1, 2, 3], [1, 2]); // true - sub array is prefix of target
81
+ * partialEqual([1, 2, 3], [1, 2]); // true - sub array is prefix of target (default behavior)
82
+ *
83
+ * // Array matching (default behavior: prefix matching)
84
+ * partialEqual([1, 2, 3, 4], [1, 2]); // true - checks first 2 elements
85
+ * partialEqual([1, 3, 4], [1, 2]); // false - second element doesn't match
86
+ *
87
+ * // Advanced array matchers for flexible matching
88
+ * partialEqual([1, 2, 3, 4, 5], match.array.contains([3, 1])); // true - contains elements anywhere
89
+ * partialEqual([1, 2, 3, 4, 5], match.array.containsInOrder([2, 4])); // true - contains in order (non-consecutive)
90
+ * partialEqual([1, 2, 3], match.array.startsWith([1, 2])); // true - explicit prefix matching
91
+ * partialEqual([1, 2, 3], match.array.endsWith([2, 3])); // true - suffix matching
92
+ * partialEqual([1, 2, 3], match.array.length(3)); // true - exact length
93
+ * partialEqual([1, 2, 3], match.array.includes(2)); // true - includes element
94
+ * partialEqual([10, 20, 30], match.array.every(match.num.isGreaterThan(5))); // true - all elements match
95
+ * partialEqual([1, 10, 3], match.array.some(match.num.isGreaterThan(8))); // true - at least one matches
70
96
  *
71
97
  * // Special comparisons
72
98
  * partialEqual('hello world', match.str.contains('world')); // true
@@ -3,7 +3,7 @@ import { Result } from 't-result';
3
3
  type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsWith', value: string] | [
4
4
  type: 'hasType',
5
5
  value: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'function'
6
- ] | [type: 'strContains', value: string] | [type: 'strMatchesRegex', value: RegExp] | [type: 'deepEqual', value: any] | [type: 'numIsGreaterThan', value: number] | [type: 'numIsGreaterThanOrEqual', value: number] | [type: 'numIsLessThan', value: number] | [type: 'numIsLessThanOrEqual', value: number] | [type: 'numIsInRange', value: [number, number]] | [type: 'jsonStringHasPartial', value: any] | [type: 'partialEqual', value: any] | [type: 'custom', value: (target: unknown) => boolean | {
6
+ ] | [type: 'strContains', value: string] | [type: 'strMatchesRegex', value: RegExp] | [type: 'deepEqual', value: any] | [type: 'numIsGreaterThan', value: number] | [type: 'numIsGreaterThanOrEqual', value: number] | [type: 'numIsLessThan', value: number] | [type: 'numIsLessThanOrEqual', value: number] | [type: 'numIsInRange', value: [number, number]] | [type: 'arrayContains', value: any[]] | [type: 'arrayContainsInOrder', value: any[]] | [type: 'arrayStartsWith', value: any[]] | [type: 'arrayEndsWith', value: any[]] | [type: 'arrayLength', value: number] | [type: 'arrayMinLength', value: number] | [type: 'arrayMaxLength', value: number] | [type: 'arrayIncludes', value: any] | [type: 'arrayEvery', value: ComparisonsType] | [type: 'arraySome', value: ComparisonsType] | [type: 'jsonStringHasPartial', value: any] | [type: 'partialEqual', value: any] | [type: 'custom', value: (target: unknown) => boolean | {
7
7
  error: string;
8
8
  }] | [type: 'isInstanceOf', value: new (...args: any[]) => any] | [type: 'keyNotBePresent', value: null] | [type: 'not', value: ComparisonsType] | [type: 'any', value: ComparisonsType[]] | [type: 'all', value: ComparisonsType[]] | [type: 'withNoExtraKeys', partialShape: any] | [type: 'withDeepNoExtraKeys', partialShape: any] | [type: 'noExtraDefinedKeys', partialShape: any] | [type: 'deepNoExtraDefinedKeys', partialShape: any];
9
9
  type Comparison = {
@@ -36,6 +36,18 @@ type BaseMatch = {
36
36
  isLessThanOrEqual: (value: number) => Comparison;
37
37
  isInRange: (value: [number, number]) => Comparison;
38
38
  };
39
+ array: {
40
+ contains: (elements: any[]) => Comparison;
41
+ containsInOrder: (elements: any[]) => Comparison;
42
+ startsWith: (elements: any[]) => Comparison;
43
+ endsWith: (elements: any[]) => Comparison;
44
+ length: (n: number) => Comparison;
45
+ minLength: (n: number) => Comparison;
46
+ maxLength: (n: number) => Comparison;
47
+ includes: (element: any) => Comparison;
48
+ every: (matcher: Comparison) => Comparison;
49
+ some: (matcher: Comparison) => Comparison;
50
+ };
39
51
  jsonString: {
40
52
  hasPartial: (value: any) => Comparison;
41
53
  };
@@ -66,7 +78,21 @@ type PartialError = {
66
78
  * @example
67
79
  * // Basic partial matching
68
80
  * partialEqual({ a: 1, b: 2 }, { a: 1 }); // true - sub is subset of target
69
- * partialEqual([1, 2, 3], [1, 2]); // true - sub array is prefix of target
81
+ * partialEqual([1, 2, 3], [1, 2]); // true - sub array is prefix of target (default behavior)
82
+ *
83
+ * // Array matching (default behavior: prefix matching)
84
+ * partialEqual([1, 2, 3, 4], [1, 2]); // true - checks first 2 elements
85
+ * partialEqual([1, 3, 4], [1, 2]); // false - second element doesn't match
86
+ *
87
+ * // Advanced array matchers for flexible matching
88
+ * partialEqual([1, 2, 3, 4, 5], match.array.contains([3, 1])); // true - contains elements anywhere
89
+ * partialEqual([1, 2, 3, 4, 5], match.array.containsInOrder([2, 4])); // true - contains in order (non-consecutive)
90
+ * partialEqual([1, 2, 3], match.array.startsWith([1, 2])); // true - explicit prefix matching
91
+ * partialEqual([1, 2, 3], match.array.endsWith([2, 3])); // true - suffix matching
92
+ * partialEqual([1, 2, 3], match.array.length(3)); // true - exact length
93
+ * partialEqual([1, 2, 3], match.array.includes(2)); // true - includes element
94
+ * partialEqual([10, 20, 30], match.array.every(match.num.isGreaterThan(5))); // true - all elements match
95
+ * partialEqual([1, 10, 3], match.array.some(match.num.isGreaterThan(8))); // true - at least one matches
70
96
  *
71
97
  * // Special comparisons
72
98
  * partialEqual('hello world', match.str.contains('world')); // true
@@ -36,6 +36,18 @@ var match = {
36
36
  isLessThanOrEqual: (value) => createComparison(["numIsLessThanOrEqual", value]),
37
37
  isInRange: (value) => createComparison(["numIsInRange", value])
38
38
  },
39
+ array: {
40
+ contains: (elements) => createComparison(["arrayContains", elements]),
41
+ containsInOrder: (elements) => createComparison(["arrayContainsInOrder", elements]),
42
+ startsWith: (elements) => createComparison(["arrayStartsWith", elements]),
43
+ endsWith: (elements) => createComparison(["arrayEndsWith", elements]),
44
+ length: (n) => createComparison(["arrayLength", n]),
45
+ minLength: (n) => createComparison(["arrayMinLength", n]),
46
+ maxLength: (n) => createComparison(["arrayMaxLength", n]),
47
+ includes: (element) => createComparison(["arrayIncludes", element]),
48
+ every: (matcher) => createComparison(["arrayEvery", matcher["~sc"]]),
49
+ some: (matcher) => createComparison(["arraySome", matcher["~sc"]])
50
+ },
39
51
  jsonString: {
40
52
  hasPartial: (value) => createComparison(["jsonStringHasPartial", value])
41
53
  },
@@ -85,6 +97,18 @@ var match = {
85
97
  isLessThanOrEqual: (value) => createComparison(["not", ["numIsLessThanOrEqual", value]]),
86
98
  isInRange: (value) => createComparison(["not", ["numIsInRange", value]])
87
99
  },
100
+ array: {
101
+ contains: (elements) => createComparison(["not", ["arrayContains", elements]]),
102
+ containsInOrder: (elements) => createComparison(["not", ["arrayContainsInOrder", elements]]),
103
+ startsWith: (elements) => createComparison(["not", ["arrayStartsWith", elements]]),
104
+ endsWith: (elements) => createComparison(["not", ["arrayEndsWith", elements]]),
105
+ length: (n) => createComparison(["not", ["arrayLength", n]]),
106
+ minLength: (n) => createComparison(["not", ["arrayMinLength", n]]),
107
+ maxLength: (n) => createComparison(["not", ["arrayMaxLength", n]]),
108
+ includes: (element) => createComparison(["not", ["arrayIncludes", element]]),
109
+ every: (matcher) => createComparison(["not", ["arrayEvery", matcher["~sc"]]]),
110
+ some: (matcher) => createComparison(["not", ["arraySome", matcher["~sc"]]])
111
+ },
88
112
  jsonString: {
89
113
  hasPartial: (value) => createComparison(["not", ["jsonStringHasPartial", value]])
90
114
  },
@@ -369,6 +393,248 @@ function executeComparison(target, comparison, context) {
369
393
  return checkNoExtraDefinedKeys(target, value, context, false);
370
394
  case "deepNoExtraDefinedKeys":
371
395
  return checkNoExtraDefinedKeys(target, value, context, true);
396
+ case "arrayContains": {
397
+ if (!Array.isArray(target)) {
398
+ addError(context, {
399
+ message: "Expected array",
400
+ received: target
401
+ });
402
+ return false;
403
+ }
404
+ for (const element of value) {
405
+ let found = false;
406
+ for (const targetElement of target) {
407
+ const tempContext = {
408
+ errors: [],
409
+ path: context.path
410
+ };
411
+ if (partialEqualInternal(targetElement, element, tempContext)) {
412
+ found = true;
413
+ break;
414
+ }
415
+ }
416
+ if (!found) {
417
+ addError(context, {
418
+ message: "Array does not contain expected element",
419
+ received: target,
420
+ expected: element
421
+ });
422
+ return false;
423
+ }
424
+ }
425
+ return true;
426
+ }
427
+ case "arrayContainsInOrder": {
428
+ if (!Array.isArray(target)) {
429
+ addError(context, {
430
+ message: "Expected array",
431
+ received: target
432
+ });
433
+ return false;
434
+ }
435
+ let targetIndex = 0;
436
+ for (const element of value) {
437
+ let found = false;
438
+ for (let i = targetIndex; i < target.length; i++) {
439
+ const tempContext = {
440
+ errors: [],
441
+ path: context.path
442
+ };
443
+ if (partialEqualInternal(target[i], element, tempContext)) {
444
+ targetIndex = i + 1;
445
+ found = true;
446
+ break;
447
+ }
448
+ }
449
+ if (!found) {
450
+ addError(context, {
451
+ message: "Array does not contain expected elements in order",
452
+ received: target,
453
+ expected: value
454
+ });
455
+ return false;
456
+ }
457
+ }
458
+ return true;
459
+ }
460
+ case "arrayStartsWith": {
461
+ if (!Array.isArray(target)) {
462
+ addError(context, {
463
+ message: "Expected array",
464
+ received: target
465
+ });
466
+ return false;
467
+ }
468
+ if (target.length < value.length) {
469
+ addError(context, {
470
+ message: `Array too short: expected to start with ${value.length} elements, got ${target.length}`,
471
+ received: target,
472
+ expected: value
473
+ });
474
+ return false;
475
+ }
476
+ let allMatch = true;
477
+ for (let i = 0; i < value.length; i++) {
478
+ const oldPath = context.path;
479
+ context.path = [...oldPath, `[${i}]`];
480
+ const result = partialEqualInternal(target[i], value[i], context);
481
+ context.path = oldPath;
482
+ if (!result) allMatch = false;
483
+ }
484
+ return allMatch;
485
+ }
486
+ case "arrayEndsWith": {
487
+ if (!Array.isArray(target)) {
488
+ addError(context, {
489
+ message: "Expected array",
490
+ received: target
491
+ });
492
+ return false;
493
+ }
494
+ if (target.length < value.length) {
495
+ addError(context, {
496
+ message: `Array too short: expected to end with ${value.length} elements, got ${target.length}`,
497
+ received: target,
498
+ expected: value
499
+ });
500
+ return false;
501
+ }
502
+ let allMatch = true;
503
+ const startIndex = target.length - value.length;
504
+ for (let i = 0; i < value.length; i++) {
505
+ const oldPath = context.path;
506
+ context.path = [...oldPath, `[${startIndex + i}]`];
507
+ const result = partialEqualInternal(
508
+ target[startIndex + i],
509
+ value[i],
510
+ context
511
+ );
512
+ context.path = oldPath;
513
+ if (!result) allMatch = false;
514
+ }
515
+ return allMatch;
516
+ }
517
+ case "arrayLength": {
518
+ if (!Array.isArray(target)) {
519
+ addError(context, {
520
+ message: "Expected array",
521
+ received: target
522
+ });
523
+ return false;
524
+ }
525
+ if (target.length !== value) {
526
+ addError(context, {
527
+ message: `Expected array length ${value}, got ${target.length}`,
528
+ received: target
529
+ });
530
+ return false;
531
+ }
532
+ return true;
533
+ }
534
+ case "arrayMinLength": {
535
+ if (!Array.isArray(target)) {
536
+ addError(context, {
537
+ message: "Expected array",
538
+ received: target
539
+ });
540
+ return false;
541
+ }
542
+ if (target.length < value) {
543
+ addError(context, {
544
+ message: `Expected array with at least ${value} elements, got ${target.length}`,
545
+ received: target
546
+ });
547
+ return false;
548
+ }
549
+ return true;
550
+ }
551
+ case "arrayMaxLength": {
552
+ if (!Array.isArray(target)) {
553
+ addError(context, {
554
+ message: "Expected array",
555
+ received: target
556
+ });
557
+ return false;
558
+ }
559
+ if (target.length > value) {
560
+ addError(context, {
561
+ message: `Expected array with at most ${value} elements, got ${target.length}`,
562
+ received: target
563
+ });
564
+ return false;
565
+ }
566
+ return true;
567
+ }
568
+ case "arrayIncludes": {
569
+ if (!Array.isArray(target)) {
570
+ addError(context, {
571
+ message: "Expected array",
572
+ received: target
573
+ });
574
+ return false;
575
+ }
576
+ let found = false;
577
+ for (const targetElement of target) {
578
+ const tempContext = {
579
+ errors: [],
580
+ path: context.path
581
+ };
582
+ if (partialEqualInternal(targetElement, value, tempContext)) {
583
+ found = true;
584
+ break;
585
+ }
586
+ }
587
+ if (!found) {
588
+ addError(context, {
589
+ message: "Array does not include expected element",
590
+ received: target,
591
+ expected: value
592
+ });
593
+ return false;
594
+ }
595
+ return true;
596
+ }
597
+ case "arrayEvery": {
598
+ if (!Array.isArray(target)) {
599
+ addError(context, {
600
+ message: "Expected array",
601
+ received: target
602
+ });
603
+ return false;
604
+ }
605
+ let allMatch = true;
606
+ for (let i = 0; i < target.length; i++) {
607
+ const oldPath = context.path;
608
+ context.path = [...oldPath, `[${i}]`];
609
+ const result = executeComparison(target[i], value, context);
610
+ context.path = oldPath;
611
+ if (!result) allMatch = false;
612
+ }
613
+ return allMatch;
614
+ }
615
+ case "arraySome": {
616
+ if (!Array.isArray(target)) {
617
+ addError(context, {
618
+ message: "Expected array",
619
+ received: target
620
+ });
621
+ return false;
622
+ }
623
+ for (let i = 0; i < target.length; i++) {
624
+ const tempContext = {
625
+ errors: [],
626
+ path: [...context.path, `[${i}]`]
627
+ };
628
+ if (executeComparison(target[i], value, tempContext)) {
629
+ return true;
630
+ }
631
+ }
632
+ addError(context, {
633
+ message: "No array element matches the condition",
634
+ received: target
635
+ });
636
+ return false;
637
+ }
372
638
  default:
373
639
  throw exhaustiveCheck(type);
374
640
  }
@@ -425,9 +691,8 @@ function partialEqualInternal(target, sub, context) {
425
691
  }
426
692
  if (isComparison(sub) && sub["~sc"][0] === "keyNotBePresent") {
427
693
  addError(context, {
428
- message: "Key should not be present",
429
- received: target,
430
- expected: "key not present"
694
+ message: "This property should not be present",
695
+ received: target
431
696
  });
432
697
  return false;
433
698
  }
@@ -501,9 +766,9 @@ function partialEqualInternal(target, sub, context) {
501
766
  if (target instanceof Map && sub instanceof Map) {
502
767
  if (sub.size > target.size) {
503
768
  addError(context, {
504
- message: "Map too small",
505
- received: target,
506
- expected: sub
769
+ message: "Map has less entries than expected",
770
+ received: `${target.size} entries`,
771
+ expected: `${sub.size} entries`
507
772
  });
508
773
  return false;
509
774
  }
@@ -586,8 +851,7 @@ function partialEqualInternal(target, sub, context) {
586
851
  context.path = [...oldPath2, key];
587
852
  addError(context, {
588
853
  message: "Key should not be present",
589
- received: target[key],
590
- expected: "key not present"
854
+ received: target[key]
591
855
  });
592
856
  context.path = oldPath2;
593
857
  allMatch = false;
@@ -611,8 +875,8 @@ function partialEqualInternal(target, sub, context) {
611
875
  context.path = [...oldPath2, key];
612
876
  addError(context, {
613
877
  message: "Missing property",
614
- received: void 0,
615
- expected: sub[key]
878
+ expected: sub[key],
879
+ received: { objectWithKeys: Object.keys(target) }
616
880
  });
617
881
  context.path = oldPath2;
618
882
  allMatch = false;
@@ -653,7 +917,8 @@ function checkNoExtraKeys(target, partialShape, context, deep) {
653
917
  const oldPath = context.path;
654
918
  context.path = [...oldPath, key];
655
919
  addError(context, {
656
- message: `Extra key "${key}" not expected`
920
+ message: `Extra key "${key}" should not be present`,
921
+ received: target[key]
657
922
  });
658
923
  context.path = oldPath;
659
924
  return false;
@@ -694,7 +959,8 @@ function checkNoExtraDefinedKeys(target, partialShape, context, deep) {
694
959
  const oldPath = context.path;
695
960
  context.path = [...oldPath, key];
696
961
  addError(context, {
697
- message: `Extra defined key "${key}" not expected`
962
+ message: `Extra defined key "${key}" should not be present`,
963
+ received: target[key]
698
964
  });
699
965
  context.path = oldPath;
700
966
  return false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ls-stack/utils",
3
3
  "description": "Universal TypeScript utilities for browser and Node.js",
4
- "version": "3.49.0",
4
+ "version": "3.50.0",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "dist",