@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.
- package/dist/partialEqual.cjs +278 -12
- package/dist/partialEqual.d.cts +28 -2
- package/dist/partialEqual.d.ts +28 -2
- package/dist/partialEqual.js +278 -12
- package/package.json +1 -1
package/dist/partialEqual.cjs
CHANGED
|
@@ -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: "
|
|
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
|
|
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
|
-
|
|
642
|
-
|
|
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
|
|
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
|
|
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;
|
package/dist/partialEqual.d.cts
CHANGED
|
@@ -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
|
package/dist/partialEqual.d.ts
CHANGED
|
@@ -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
|
package/dist/partialEqual.js
CHANGED
|
@@ -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: "
|
|
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
|
|
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
|
-
|
|
615
|
-
|
|
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
|
|
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
|
|
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;
|