@ls-stack/utils 3.49.1 → 3.51.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.
@@ -0,0 +1,151 @@
1
+ import {
2
+ typedObjectEntries
3
+ } from "./chunk-GMJTLFM6.js";
4
+ import {
5
+ sortBy
6
+ } from "./chunk-27AL66CH.js";
7
+
8
+ // src/objUtils.ts
9
+ import { Result } from "t-result";
10
+ function objectTypedEntries(obj) {
11
+ return Object.entries(obj);
12
+ }
13
+ function pick(obj, keys) {
14
+ const result = {};
15
+ for (const key of keys) {
16
+ result[key] = obj[key];
17
+ }
18
+ return result;
19
+ }
20
+ function mapArrayToObject(array, mapper) {
21
+ return Object.fromEntries(array.map(mapper));
22
+ }
23
+ function mapObjectToObject(obj, mapper) {
24
+ return Object.fromEntries(
25
+ objectTypedEntries(obj).map(([key, value]) => mapper(key, value))
26
+ );
27
+ }
28
+ function omit(obj, keys) {
29
+ const result = {};
30
+ for (const key of Object.keys(obj)) {
31
+ if (!keys.includes(key)) {
32
+ result[key] = obj[key];
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+ function looseGetObjectProperty(obj, key) {
38
+ return obj[key];
39
+ }
40
+ function rejectObjUndefinedValues(obj) {
41
+ const result = {};
42
+ for (const key in obj) {
43
+ if (obj[key] !== void 0) {
44
+ result[key] = obj[key];
45
+ }
46
+ }
47
+ return result;
48
+ }
49
+ function filterObjectKeys(obj, predicate) {
50
+ return Object.fromEntries(
51
+ Object.entries(obj).filter(
52
+ ([key, value]) => predicate(key, value)
53
+ )
54
+ );
55
+ }
56
+ function sortObjectKeys(obj, sortByFn, options) {
57
+ return Object.fromEntries(
58
+ sortBy(typedObjectEntries(obj), sortByFn, options)
59
+ );
60
+ }
61
+ function getValueFromPath(obj, path) {
62
+ if (!path.trim()) {
63
+ return Result.err(new Error("Path cannot be empty"));
64
+ }
65
+ const segments = parsePath(path);
66
+ let current = obj;
67
+ for (let i = 0; i < segments.length; i++) {
68
+ const segment = segments[i];
69
+ if (!segment) {
70
+ return Result.err(new Error("Invalid empty segment in path"));
71
+ }
72
+ if (current == null) {
73
+ return Result.err(
74
+ new Error(`Cannot access property '${segment}' on null or undefined`)
75
+ );
76
+ }
77
+ if (isNumericString(segment)) {
78
+ if (!Array.isArray(current)) {
79
+ return Result.err(
80
+ new Error(
81
+ `Cannot access array index '${segment}' on non-array value`
82
+ )
83
+ );
84
+ }
85
+ const index = parseInt(segment, 10);
86
+ if (index < 0 || index >= current.length) {
87
+ return Result.err(new Error(`Array index '${index}' out of bounds`));
88
+ }
89
+ current = current[index];
90
+ } else {
91
+ if (typeof current !== "object" || current === null) {
92
+ return Result.err(
93
+ new Error(`Cannot access property '${segment}' on non-object value`)
94
+ );
95
+ }
96
+ if (!(segment in current)) {
97
+ return Result.err(new Error(`Property '${segment}' not found`));
98
+ }
99
+ current = current[segment];
100
+ }
101
+ }
102
+ return Result.ok(current);
103
+ }
104
+ function parsePath(path) {
105
+ const segments = [];
106
+ let current = "";
107
+ let inBrackets = false;
108
+ for (let i = 0; i < path.length; i++) {
109
+ const char = path[i];
110
+ if (char === "[") {
111
+ if (current) {
112
+ segments.push(current);
113
+ current = "";
114
+ }
115
+ inBrackets = true;
116
+ } else if (char === "]") {
117
+ if (inBrackets && current) {
118
+ segments.push(current);
119
+ current = "";
120
+ }
121
+ inBrackets = false;
122
+ } else if (char === "." && !inBrackets) {
123
+ if (current) {
124
+ segments.push(current);
125
+ current = "";
126
+ }
127
+ } else {
128
+ current += char;
129
+ }
130
+ }
131
+ if (current) {
132
+ segments.push(current);
133
+ }
134
+ return segments;
135
+ }
136
+ function isNumericString(str) {
137
+ return /^\d+$/.test(str);
138
+ }
139
+
140
+ export {
141
+ objectTypedEntries,
142
+ pick,
143
+ mapArrayToObject,
144
+ mapObjectToObject,
145
+ omit,
146
+ looseGetObjectProperty,
147
+ rejectObjUndefinedValues,
148
+ filterObjectKeys,
149
+ sortObjectKeys,
150
+ getValueFromPath
151
+ };
package/dist/objUtils.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var objUtils_exports = {};
22
22
  __export(objUtils_exports, {
23
23
  filterObjectKeys: () => filterObjectKeys,
24
+ getValueFromPath: () => getValueFromPath,
24
25
  looseGetObjectProperty: () => looseGetObjectProperty,
25
26
  mapArrayToObject: () => mapArrayToObject,
26
27
  mapObjectToObject: () => mapObjectToObject,
@@ -31,6 +32,7 @@ __export(objUtils_exports, {
31
32
  sortObjectKeys: () => sortObjectKeys
32
33
  });
33
34
  module.exports = __toCommonJS(objUtils_exports);
35
+ var import_t_result = require("t-result");
34
36
 
35
37
  // src/arrayUtils.ts
36
38
  function sortBy(arr, sortByValue, props = "asc") {
@@ -115,9 +117,88 @@ function sortObjectKeys(obj, sortByFn, options) {
115
117
  sortBy(typedObjectEntries(obj), sortByFn, options)
116
118
  );
117
119
  }
120
+ function getValueFromPath(obj, path) {
121
+ if (!path.trim()) {
122
+ return import_t_result.Result.err(new Error("Path cannot be empty"));
123
+ }
124
+ const segments = parsePath(path);
125
+ let current = obj;
126
+ for (let i = 0; i < segments.length; i++) {
127
+ const segment = segments[i];
128
+ if (!segment) {
129
+ return import_t_result.Result.err(new Error("Invalid empty segment in path"));
130
+ }
131
+ if (current == null) {
132
+ return import_t_result.Result.err(
133
+ new Error(`Cannot access property '${segment}' on null or undefined`)
134
+ );
135
+ }
136
+ if (isNumericString(segment)) {
137
+ if (!Array.isArray(current)) {
138
+ return import_t_result.Result.err(
139
+ new Error(
140
+ `Cannot access array index '${segment}' on non-array value`
141
+ )
142
+ );
143
+ }
144
+ const index = parseInt(segment, 10);
145
+ if (index < 0 || index >= current.length) {
146
+ return import_t_result.Result.err(new Error(`Array index '${index}' out of bounds`));
147
+ }
148
+ current = current[index];
149
+ } else {
150
+ if (typeof current !== "object" || current === null) {
151
+ return import_t_result.Result.err(
152
+ new Error(`Cannot access property '${segment}' on non-object value`)
153
+ );
154
+ }
155
+ if (!(segment in current)) {
156
+ return import_t_result.Result.err(new Error(`Property '${segment}' not found`));
157
+ }
158
+ current = current[segment];
159
+ }
160
+ }
161
+ return import_t_result.Result.ok(current);
162
+ }
163
+ function parsePath(path) {
164
+ const segments = [];
165
+ let current = "";
166
+ let inBrackets = false;
167
+ for (let i = 0; i < path.length; i++) {
168
+ const char = path[i];
169
+ if (char === "[") {
170
+ if (current) {
171
+ segments.push(current);
172
+ current = "";
173
+ }
174
+ inBrackets = true;
175
+ } else if (char === "]") {
176
+ if (inBrackets && current) {
177
+ segments.push(current);
178
+ current = "";
179
+ }
180
+ inBrackets = false;
181
+ } else if (char === "." && !inBrackets) {
182
+ if (current) {
183
+ segments.push(current);
184
+ current = "";
185
+ }
186
+ } else {
187
+ current += char;
188
+ }
189
+ }
190
+ if (current) {
191
+ segments.push(current);
192
+ }
193
+ return segments;
194
+ }
195
+ function isNumericString(str) {
196
+ return /^\d+$/.test(str);
197
+ }
118
198
  // Annotate the CommonJS export names for ESM import in node:
119
199
  0 && (module.exports = {
120
200
  filterObjectKeys,
201
+ getValueFromPath,
121
202
  looseGetObjectProperty,
122
203
  mapArrayToObject,
123
204
  mapObjectToObject,
@@ -1,3 +1,4 @@
1
+ import { Result } from 't-result';
1
2
  import { SortByValueFn, SortByProps } from './arrayUtils.cjs';
2
3
  import { MakeUndefinedKeysOptional } from './typeUtils.cjs';
3
4
 
@@ -14,5 +15,6 @@ declare function looseGetObjectProperty<T extends Record<string, unknown>>(obj:
14
15
  declare function rejectObjUndefinedValues<T extends Record<string, unknown>>(obj: T): MakeUndefinedKeysOptional<T>;
15
16
  declare function filterObjectKeys<T extends Record<string, unknown>>(obj: T, predicate: (key: keyof T, value: T[keyof T]) => boolean): Partial<T>;
16
17
  declare function sortObjectKeys<T extends Record<string, unknown>>(obj: T, sortByFn: SortByValueFn<[key: keyof T, value: T[keyof T]]>, options?: SortByProps): T;
18
+ declare function getValueFromPath(obj: Record<string, unknown>, path: string): Result<unknown, Error>;
17
19
 
18
- export { filterObjectKeys, looseGetObjectProperty, mapArrayToObject, mapObjectToObject, objectTypedEntries, omit, pick, rejectObjUndefinedValues, sortObjectKeys };
20
+ export { filterObjectKeys, getValueFromPath, looseGetObjectProperty, mapArrayToObject, mapObjectToObject, objectTypedEntries, omit, pick, rejectObjUndefinedValues, sortObjectKeys };
@@ -1,3 +1,4 @@
1
+ import { Result } from 't-result';
1
2
  import { SortByValueFn, SortByProps } from './arrayUtils.js';
2
3
  import { MakeUndefinedKeysOptional } from './typeUtils.js';
3
4
 
@@ -14,5 +15,6 @@ declare function looseGetObjectProperty<T extends Record<string, unknown>>(obj:
14
15
  declare function rejectObjUndefinedValues<T extends Record<string, unknown>>(obj: T): MakeUndefinedKeysOptional<T>;
15
16
  declare function filterObjectKeys<T extends Record<string, unknown>>(obj: T, predicate: (key: keyof T, value: T[keyof T]) => boolean): Partial<T>;
16
17
  declare function sortObjectKeys<T extends Record<string, unknown>>(obj: T, sortByFn: SortByValueFn<[key: keyof T, value: T[keyof T]]>, options?: SortByProps): T;
18
+ declare function getValueFromPath(obj: Record<string, unknown>, path: string): Result<unknown, Error>;
17
19
 
18
- export { filterObjectKeys, looseGetObjectProperty, mapArrayToObject, mapObjectToObject, objectTypedEntries, omit, pick, rejectObjUndefinedValues, sortObjectKeys };
20
+ export { filterObjectKeys, getValueFromPath, looseGetObjectProperty, mapArrayToObject, mapObjectToObject, objectTypedEntries, omit, pick, rejectObjUndefinedValues, sortObjectKeys };
package/dist/objUtils.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  filterObjectKeys,
3
+ getValueFromPath,
3
4
  looseGetObjectProperty,
4
5
  mapArrayToObject,
5
6
  mapObjectToObject,
@@ -8,13 +9,14 @@ import {
8
9
  pick,
9
10
  rejectObjUndefinedValues,
10
11
  sortObjectKeys
11
- } from "./chunk-Y45CE75W.js";
12
+ } from "./chunk-Y76LZUOB.js";
12
13
  import "./chunk-GMJTLFM6.js";
13
14
  import "./chunk-27AL66CH.js";
14
15
  import "./chunk-C2SVCIWE.js";
15
16
  import "./chunk-JF2MDHOJ.js";
16
17
  export {
17
18
  filterObjectKeys,
19
+ getValueFromPath,
18
20
  looseGetObjectProperty,
19
21
  mapArrayToObject,
20
22
  mapObjectToObject,
@@ -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
  }
@@ -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
  }
@@ -757,6 +757,7 @@ function clampMin(value, min) {
757
757
  }
758
758
 
759
759
  // src/objUtils.ts
760
+ var import_t_result = require("t-result");
760
761
  function pick(obj, keys) {
761
762
  const result = {};
762
763
  for (const key of keys) {
package/dist/testUtils.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  omit,
6
6
  pick
7
- } from "./chunk-Y45CE75W.js";
7
+ } from "./chunk-Y76LZUOB.js";
8
8
  import "./chunk-GMJTLFM6.js";
9
9
  import {
10
10
  filterObjectOrArrayKeys
@@ -56,5 +56,6 @@ type PickRequiredKeys<T> = {
56
56
  [K in keyof T]: undefined extends T[K] ? never : K;
57
57
  }[keyof T];
58
58
  type MakeUndefinedKeysOptional<T> = Prettify<Partial<Pick<T, PickUndefinedKeys<T>>> & Pick<T, PickRequiredKeys<T>>>;
59
+ type StringWithAutoComplete<T extends string> = T | (string & {});
59
60
 
60
- export type { DeepPrettify, DeepReplaceValue, DefaultSkipTransverseDeepReplace, IsAny, MakeUndefinedKeysOptional, NonPartial, ObjKeysWithValuesOfType, PartialPossiblyUndefinedValues, PartialRecord, Prettify };
61
+ export type { DeepPrettify, DeepReplaceValue, DefaultSkipTransverseDeepReplace, IsAny, MakeUndefinedKeysOptional, NonPartial, ObjKeysWithValuesOfType, PartialPossiblyUndefinedValues, PartialRecord, Prettify, StringWithAutoComplete };
@@ -56,5 +56,6 @@ type PickRequiredKeys<T> = {
56
56
  [K in keyof T]: undefined extends T[K] ? never : K;
57
57
  }[keyof T];
58
58
  type MakeUndefinedKeysOptional<T> = Prettify<Partial<Pick<T, PickUndefinedKeys<T>>> & Pick<T, PickRequiredKeys<T>>>;
59
+ type StringWithAutoComplete<T extends string> = T | (string & {});
59
60
 
60
- export type { DeepPrettify, DeepReplaceValue, DefaultSkipTransverseDeepReplace, IsAny, MakeUndefinedKeysOptional, NonPartial, ObjKeysWithValuesOfType, PartialPossiblyUndefinedValues, PartialRecord, Prettify };
61
+ export type { DeepPrettify, DeepReplaceValue, DefaultSkipTransverseDeepReplace, IsAny, MakeUndefinedKeysOptional, NonPartial, ObjKeysWithValuesOfType, PartialPossiblyUndefinedValues, PartialRecord, Prettify, StringWithAutoComplete };
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.1",
4
+ "version": "3.51.0",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "dist",
@@ -1,71 +0,0 @@
1
- import {
2
- typedObjectEntries
3
- } from "./chunk-GMJTLFM6.js";
4
- import {
5
- sortBy
6
- } from "./chunk-27AL66CH.js";
7
-
8
- // src/objUtils.ts
9
- function objectTypedEntries(obj) {
10
- return Object.entries(obj);
11
- }
12
- function pick(obj, keys) {
13
- const result = {};
14
- for (const key of keys) {
15
- result[key] = obj[key];
16
- }
17
- return result;
18
- }
19
- function mapArrayToObject(array, mapper) {
20
- return Object.fromEntries(array.map(mapper));
21
- }
22
- function mapObjectToObject(obj, mapper) {
23
- return Object.fromEntries(
24
- objectTypedEntries(obj).map(([key, value]) => mapper(key, value))
25
- );
26
- }
27
- function omit(obj, keys) {
28
- const result = {};
29
- for (const key of Object.keys(obj)) {
30
- if (!keys.includes(key)) {
31
- result[key] = obj[key];
32
- }
33
- }
34
- return result;
35
- }
36
- function looseGetObjectProperty(obj, key) {
37
- return obj[key];
38
- }
39
- function rejectObjUndefinedValues(obj) {
40
- const result = {};
41
- for (const key in obj) {
42
- if (obj[key] !== void 0) {
43
- result[key] = obj[key];
44
- }
45
- }
46
- return result;
47
- }
48
- function filterObjectKeys(obj, predicate) {
49
- return Object.fromEntries(
50
- Object.entries(obj).filter(
51
- ([key, value]) => predicate(key, value)
52
- )
53
- );
54
- }
55
- function sortObjectKeys(obj, sortByFn, options) {
56
- return Object.fromEntries(
57
- sortBy(typedObjectEntries(obj), sortByFn, options)
58
- );
59
- }
60
-
61
- export {
62
- objectTypedEntries,
63
- pick,
64
- mapArrayToObject,
65
- mapObjectToObject,
66
- omit,
67
- looseGetObjectProperty,
68
- rejectObjUndefinedValues,
69
- filterObjectKeys,
70
- sortObjectKeys
71
- };