@ls-stack/utils 3.53.0 → 3.55.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.
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-IATIXMCE.js";
4
4
  import {
5
5
  truncateString
6
- } from "./chunk-B3KFV2MH.js";
6
+ } from "./chunk-BM4PYVOX.js";
7
7
  import {
8
8
  isObject,
9
9
  isPlainObject
@@ -22,6 +22,33 @@ function formatNum(num, maxDecimalsOrOptions = 2) {
22
22
  function isSnakeCase(str) {
23
23
  return /^[a-z0-9_]+$/.test(str);
24
24
  }
25
+ function isKebabCase(str) {
26
+ return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(str);
27
+ }
28
+ function isPascalCase(str) {
29
+ return /^[A-Z][a-zA-Z0-9]*$/.test(str);
30
+ }
31
+ function isCamelCase(str) {
32
+ return /^[a-z][a-zA-Z0-9]*$/.test(str);
33
+ }
34
+ function isTitleCase(str) {
35
+ return /^[A-Z][a-z0-9]*( ([A-Z][a-z0-9]*|[0-9]+))*$/.test(str);
36
+ }
37
+ function isSentenceCase(str) {
38
+ return /^[A-Z][a-z0-9]*( [a-z0-9]+)*$/.test(str);
39
+ }
40
+ function isConstantCase(str) {
41
+ return /^[A-Z_][A-Z0-9_]*$/.test(str);
42
+ }
43
+ function isDotCase(str) {
44
+ return /^[a-z0-9]+(\.[a-z0-9]+)*$/.test(str);
45
+ }
46
+ function isPathCase(str) {
47
+ return /^[a-z0-9]+(\/[a-z0-9]+)*$/.test(str);
48
+ }
49
+ function convertToKebabCase(str) {
50
+ return convertToSnakeCase(str).replace(/_/g, "-");
51
+ }
25
52
  function convertToSnakeCase(str) {
26
53
  return str.replace(/[\s\-.]+/g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9_]/g, "").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
27
54
  }
@@ -38,6 +65,15 @@ function convertToSentenceCase(str) {
38
65
  function convertToTitleCase(str) {
39
66
  return str.replace(/[\s\-.]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").split(/[\s_-]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
40
67
  }
68
+ function convertToConstantCase(str) {
69
+ return convertToSnakeCase(str).toUpperCase();
70
+ }
71
+ function convertToDotCase(str) {
72
+ return convertToSnakeCase(str).replace(/_/g, ".");
73
+ }
74
+ function convertToPathCase(str) {
75
+ return convertToSnakeCase(str).replace(/_/g, "/");
76
+ }
41
77
  function truncateString(str, length, ellipsis = "\u2026") {
42
78
  if (str.length <= length) return str;
43
79
  return str.slice(0, length - 1) + ellipsis;
@@ -51,11 +87,23 @@ export {
51
87
  joinStrings,
52
88
  formatNum,
53
89
  isSnakeCase,
90
+ isKebabCase,
91
+ isPascalCase,
92
+ isCamelCase,
93
+ isTitleCase,
94
+ isSentenceCase,
95
+ isConstantCase,
96
+ isDotCase,
97
+ isPathCase,
98
+ convertToKebabCase,
54
99
  convertToSnakeCase,
55
100
  convertToPascalCase,
56
101
  convertToCamelCase,
57
102
  convertToSentenceCase,
58
103
  convertToTitleCase,
104
+ convertToConstantCase,
105
+ convertToDotCase,
106
+ convertToPathCase,
59
107
  truncateString,
60
108
  removeANSIColors
61
109
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-VAAMRG4K.js";
4
4
  import {
5
5
  truncateString
6
- } from "./chunk-B3KFV2MH.js";
6
+ } from "./chunk-BM4PYVOX.js";
7
7
  import {
8
8
  sleep
9
9
  } from "./chunk-5DZT3Z5Z.js";
@@ -100,6 +100,15 @@ var match = {
100
100
  return ["deepEqual", v];
101
101
  })
102
102
  ]),
103
+ key: {
104
+ any: "$pqkc:any$",
105
+ anyOther: "$pqkc:anyOther$",
106
+ numeric: "$pqkc:numeric$",
107
+ startingWith: (substring) => `$pqkc:startingWith:${substring}$`,
108
+ endingWith: (substring) => `$pqkc:endingWith:${substring}$`,
109
+ containing: (substring) => `$pqkc:contains:${substring}$`,
110
+ matchingRegex: (regex) => `$pqkc:matchesRegex:${regex}$`
111
+ },
103
112
  not: {
104
113
  hasType: {
105
114
  string: createComparison(["not", ["hasType", "string"]]),
@@ -169,12 +178,93 @@ var match = {
169
178
  noExtraKeys: (partialShape) => createComparison(["not", ["withNoExtraKeys", partialShape]]),
170
179
  deepNoExtraKeys: (partialShape) => createComparison(["not", ["withDeepNoExtraKeys", partialShape]]),
171
180
  noExtraDefinedKeys: (partialShape) => createComparison(["not", ["noExtraDefinedKeys", partialShape]]),
172
- deepNoExtraDefinedKeys: (partialShape) => createComparison(["not", ["deepNoExtraDefinedKeys", partialShape]])
181
+ deepNoExtraDefinedKeys: (partialShape) => createComparison(["not", ["deepNoExtraDefinedKeys", partialShape]]),
182
+ key: {
183
+ any: "$pqkc-not:any$",
184
+ anyOther: "$pqkc-not:anyOther$",
185
+ numeric: "$pqkc-not:numeric$",
186
+ startingWith: (substring) => `$pqkc-not:startingWith:${substring}$`,
187
+ endingWith: (substring) => `$pqkc-not:endingWith:${substring}$`,
188
+ containing: (substring) => `$pqkc-not:contains:${substring}$`,
189
+ matchingRegex: (regex) => `$pqkc-not:matchesRegex:${regex}$`
190
+ }
173
191
  }
174
192
  };
175
193
  function isComparison(value) {
176
194
  return value && typeof value === "object" && "~sc" in value;
177
195
  }
196
+ function isKeyMatcher(key) {
197
+ return typeof key === "string" && (key.startsWith("$pqkc:") || key.startsWith("$pqkc-not:"));
198
+ }
199
+ function parseKeyMatcher(key) {
200
+ if (!isKeyMatcher(key)) return null;
201
+ const negated = key.startsWith("$pqkc-not:");
202
+ const prefix = negated ? "$pqkc-not:" : "$pqkc:";
203
+ const suffix = "$";
204
+ if (!key.endsWith(suffix)) return null;
205
+ const content = key.slice(prefix.length, -suffix.length);
206
+ if (content === "any") {
207
+ return { type: "any", negated };
208
+ }
209
+ if (content === "anyOther") {
210
+ return { type: "anyOther", negated };
211
+ }
212
+ if (content === "numeric") {
213
+ return { type: "numeric", negated };
214
+ }
215
+ if (content.startsWith("startingWith:")) {
216
+ return { type: "startingWith", parameter: content.slice("startingWith:".length), negated };
217
+ }
218
+ if (content.startsWith("endingWith:")) {
219
+ return { type: "endingWith", parameter: content.slice("endingWith:".length), negated };
220
+ }
221
+ if (content.startsWith("contains:")) {
222
+ return { type: "contains", parameter: content.slice("contains:".length), negated };
223
+ }
224
+ if (content.startsWith("matchesRegex:")) {
225
+ const regexStr = content.slice("matchesRegex:".length);
226
+ try {
227
+ if (regexStr.startsWith("/") && regexStr.lastIndexOf("/") > 0) {
228
+ const lastSlashIndex = regexStr.lastIndexOf("/");
229
+ const pattern = regexStr.slice(1, lastSlashIndex);
230
+ const flags = regexStr.slice(lastSlashIndex + 1);
231
+ return { type: "matchesRegex", parameter: new RegExp(pattern, flags), negated };
232
+ } else {
233
+ return { type: "matchesRegex", parameter: new RegExp(regexStr), negated };
234
+ }
235
+ } catch {
236
+ return null;
237
+ }
238
+ }
239
+ return null;
240
+ }
241
+ function keyMatchesPattern(key, matcher) {
242
+ let matches = false;
243
+ switch (matcher.type) {
244
+ case "any":
245
+ matches = true;
246
+ break;
247
+ case "anyOther":
248
+ matches = true;
249
+ break;
250
+ case "numeric":
251
+ matches = /^\d+$/.test(key);
252
+ break;
253
+ case "startingWith":
254
+ matches = key.startsWith(matcher.parameter);
255
+ break;
256
+ case "endingWith":
257
+ matches = key.endsWith(matcher.parameter);
258
+ break;
259
+ case "contains":
260
+ matches = key.includes(matcher.parameter);
261
+ break;
262
+ case "matchesRegex":
263
+ matches = matcher.parameter.test(key);
264
+ break;
265
+ }
266
+ return matcher.negated ? !matches : matches;
267
+ }
178
268
  function executeComparison(target, comparison, context) {
179
269
  const [type, value] = comparison;
180
270
  switch (type) {
@@ -871,7 +961,21 @@ function partialEqualInternal(target, sub, context) {
871
961
  return false;
872
962
  }
873
963
  let allMatch = true;
964
+ const regularKeys = [];
965
+ const keyMatchers = [];
874
966
  for (const key of Object.keys(sub)) {
967
+ if (isKeyMatcher(key)) {
968
+ const matcher = parseKeyMatcher(key);
969
+ if (matcher) {
970
+ keyMatchers.push({ key, matcher, value: sub[key] });
971
+ }
972
+ } else {
973
+ regularKeys.push(key);
974
+ }
975
+ }
976
+ const explicitlySpecifiedKeys = /* @__PURE__ */ new Set();
977
+ for (const key of regularKeys) {
978
+ explicitlySpecifiedKeys.add(key);
875
979
  if (isComparison(sub[key]) && sub[key]["~sc"][0] === "keyNotBePresent") {
876
980
  if (has.call(target, key)) {
877
981
  const oldPath2 = context.path;
@@ -915,6 +1019,72 @@ function partialEqualInternal(target, sub, context) {
915
1019
  context.path = oldPath;
916
1020
  if (!result) allMatch = false;
917
1021
  }
1022
+ for (const { matcher, value } of keyMatchers) {
1023
+ let foundMatch = false;
1024
+ const matchedKeys = [];
1025
+ if (matcher.type === "anyOther") {
1026
+ for (const targetKey of Object.keys(target)) {
1027
+ if (!explicitlySpecifiedKeys.has(targetKey)) {
1028
+ const matches = keyMatchesPattern(targetKey, matcher);
1029
+ if (matches) {
1030
+ foundMatch = true;
1031
+ matchedKeys.push(targetKey);
1032
+ const oldPath = context.path;
1033
+ context.path = [...oldPath, targetKey];
1034
+ const result = partialEqualInternal(target[targetKey], value, context);
1035
+ context.path = oldPath;
1036
+ if (!result) allMatch = false;
1037
+ }
1038
+ }
1039
+ }
1040
+ } else if (matcher.type === "any") {
1041
+ let anyKeyMatched = false;
1042
+ for (const targetKey of Object.keys(target)) {
1043
+ if (keyMatchesPattern(targetKey, matcher)) {
1044
+ foundMatch = true;
1045
+ matchedKeys.push(targetKey);
1046
+ explicitlySpecifiedKeys.add(targetKey);
1047
+ const tempContext = {
1048
+ errors: [],
1049
+ path: [...context.path, targetKey]
1050
+ };
1051
+ const result = partialEqualInternal(target[targetKey], value, tempContext);
1052
+ if (result) {
1053
+ anyKeyMatched = true;
1054
+ break;
1055
+ }
1056
+ }
1057
+ }
1058
+ if (foundMatch && !anyKeyMatched) {
1059
+ addError(context, {
1060
+ message: `No keys matching pattern had the expected value`,
1061
+ received: { keysChecked: matchedKeys, availableKeys: Object.keys(target) }
1062
+ });
1063
+ allMatch = false;
1064
+ }
1065
+ } else {
1066
+ for (const targetKey of Object.keys(target)) {
1067
+ if (keyMatchesPattern(targetKey, matcher)) {
1068
+ foundMatch = true;
1069
+ matchedKeys.push(targetKey);
1070
+ explicitlySpecifiedKeys.add(targetKey);
1071
+ const oldPath = context.path;
1072
+ context.path = [...oldPath, targetKey];
1073
+ const result = partialEqualInternal(target[targetKey], value, context);
1074
+ context.path = oldPath;
1075
+ if (!result) allMatch = false;
1076
+ }
1077
+ }
1078
+ }
1079
+ if (!foundMatch) {
1080
+ const patternDescription = matcher.type === "anyOther" ? "anyOther (keys not explicitly specified)" : `${matcher.type}${matcher.parameter ? ` (${matcher.parameter})` : ""}`;
1081
+ addError(context, {
1082
+ message: `No keys found matching pattern: ${patternDescription}`,
1083
+ received: { availableKeys: Object.keys(target), explicitKeys: Array.from(explicitlySpecifiedKeys) }
1084
+ });
1085
+ allMatch = false;
1086
+ }
1087
+ }
918
1088
  return allMatch;
919
1089
  }
920
1090
  if (target !== sub) {
@@ -6,6 +6,16 @@ type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsW
6
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
+ type KeyComparisonPrefix = 'pqkc' | 'pqkc-not';
10
+ type KeyComparison = {
11
+ any: `$${KeyComparisonPrefix}:any$`;
12
+ anyOther: `$${KeyComparisonPrefix}:anyOther$`;
13
+ numeric: `$${KeyComparisonPrefix}:numeric$`;
14
+ startingWith: `$${KeyComparisonPrefix}:startingWith:${string}$`;
15
+ endingWith: `$${KeyComparisonPrefix}:endingWith:${string}$`;
16
+ contains: `$${KeyComparisonPrefix}:contains:${string}$`;
17
+ matchesRegex: `$${KeyComparisonPrefix}:matchesRegex:${string}$`;
18
+ };
9
19
  type Comparison = {
10
20
  '~sc': ComparisonsType;
11
21
  };
@@ -59,6 +69,15 @@ type BaseMatch = {
59
69
  keyNotBePresent: Comparison;
60
70
  any: (...values: any[]) => Comparison;
61
71
  all: (...values: any[]) => Comparison;
72
+ key: {
73
+ any: KeyComparison['any'];
74
+ anyOther: KeyComparison['anyOther'];
75
+ numeric: KeyComparison['numeric'];
76
+ startingWith: (substring: string) => KeyComparison['startingWith'];
77
+ endingWith: (substring: string) => KeyComparison['endingWith'];
78
+ containing: (substring: string) => KeyComparison['contains'];
79
+ matchingRegex: (regex: RegExp) => KeyComparison['matchesRegex'];
80
+ };
62
81
  };
63
82
  type Match = BaseMatch & {
64
83
  not: BaseMatch;
@@ -91,7 +110,10 @@ type PartialError = {
91
110
  * partialEqual([1, 2, 3], match.array.endsWith([2, 3])); // true - suffix matching
92
111
  * partialEqual([1, 2, 3], match.array.length(3)); // true - exact length
93
112
  * 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
113
+ * partialEqual(
114
+ * [10, 20, 30],
115
+ * match.array.every(match.num.isGreaterThan(5)),
116
+ * ); // true - all elements match
95
117
  * partialEqual([1, 10, 3], match.array.some(match.num.isGreaterThan(8))); // true - at least one matches
96
118
  *
97
119
  * // Special comparisons
@@ -6,6 +6,16 @@ type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsW
6
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
+ type KeyComparisonPrefix = 'pqkc' | 'pqkc-not';
10
+ type KeyComparison = {
11
+ any: `$${KeyComparisonPrefix}:any$`;
12
+ anyOther: `$${KeyComparisonPrefix}:anyOther$`;
13
+ numeric: `$${KeyComparisonPrefix}:numeric$`;
14
+ startingWith: `$${KeyComparisonPrefix}:startingWith:${string}$`;
15
+ endingWith: `$${KeyComparisonPrefix}:endingWith:${string}$`;
16
+ contains: `$${KeyComparisonPrefix}:contains:${string}$`;
17
+ matchesRegex: `$${KeyComparisonPrefix}:matchesRegex:${string}$`;
18
+ };
9
19
  type Comparison = {
10
20
  '~sc': ComparisonsType;
11
21
  };
@@ -59,6 +69,15 @@ type BaseMatch = {
59
69
  keyNotBePresent: Comparison;
60
70
  any: (...values: any[]) => Comparison;
61
71
  all: (...values: any[]) => Comparison;
72
+ key: {
73
+ any: KeyComparison['any'];
74
+ anyOther: KeyComparison['anyOther'];
75
+ numeric: KeyComparison['numeric'];
76
+ startingWith: (substring: string) => KeyComparison['startingWith'];
77
+ endingWith: (substring: string) => KeyComparison['endingWith'];
78
+ containing: (substring: string) => KeyComparison['contains'];
79
+ matchingRegex: (regex: RegExp) => KeyComparison['matchesRegex'];
80
+ };
62
81
  };
63
82
  type Match = BaseMatch & {
64
83
  not: BaseMatch;
@@ -91,7 +110,10 @@ type PartialError = {
91
110
  * partialEqual([1, 2, 3], match.array.endsWith([2, 3])); // true - suffix matching
92
111
  * partialEqual([1, 2, 3], match.array.length(3)); // true - exact length
93
112
  * 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
113
+ * partialEqual(
114
+ * [10, 20, 30],
115
+ * match.array.every(match.num.isGreaterThan(5)),
116
+ * ); // true - all elements match
95
117
  * partialEqual([1, 10, 3], match.array.some(match.num.isGreaterThan(8))); // true - at least one matches
96
118
  *
97
119
  * // Special comparisons
@@ -73,6 +73,15 @@ var match = {
73
73
  return ["deepEqual", v];
74
74
  })
75
75
  ]),
76
+ key: {
77
+ any: "$pqkc:any$",
78
+ anyOther: "$pqkc:anyOther$",
79
+ numeric: "$pqkc:numeric$",
80
+ startingWith: (substring) => `$pqkc:startingWith:${substring}$`,
81
+ endingWith: (substring) => `$pqkc:endingWith:${substring}$`,
82
+ containing: (substring) => `$pqkc:contains:${substring}$`,
83
+ matchingRegex: (regex) => `$pqkc:matchesRegex:${regex}$`
84
+ },
76
85
  not: {
77
86
  hasType: {
78
87
  string: createComparison(["not", ["hasType", "string"]]),
@@ -142,12 +151,93 @@ var match = {
142
151
  noExtraKeys: (partialShape) => createComparison(["not", ["withNoExtraKeys", partialShape]]),
143
152
  deepNoExtraKeys: (partialShape) => createComparison(["not", ["withDeepNoExtraKeys", partialShape]]),
144
153
  noExtraDefinedKeys: (partialShape) => createComparison(["not", ["noExtraDefinedKeys", partialShape]]),
145
- deepNoExtraDefinedKeys: (partialShape) => createComparison(["not", ["deepNoExtraDefinedKeys", partialShape]])
154
+ deepNoExtraDefinedKeys: (partialShape) => createComparison(["not", ["deepNoExtraDefinedKeys", partialShape]]),
155
+ key: {
156
+ any: "$pqkc-not:any$",
157
+ anyOther: "$pqkc-not:anyOther$",
158
+ numeric: "$pqkc-not:numeric$",
159
+ startingWith: (substring) => `$pqkc-not:startingWith:${substring}$`,
160
+ endingWith: (substring) => `$pqkc-not:endingWith:${substring}$`,
161
+ containing: (substring) => `$pqkc-not:contains:${substring}$`,
162
+ matchingRegex: (regex) => `$pqkc-not:matchesRegex:${regex}$`
163
+ }
146
164
  }
147
165
  };
148
166
  function isComparison(value) {
149
167
  return value && typeof value === "object" && "~sc" in value;
150
168
  }
169
+ function isKeyMatcher(key) {
170
+ return typeof key === "string" && (key.startsWith("$pqkc:") || key.startsWith("$pqkc-not:"));
171
+ }
172
+ function parseKeyMatcher(key) {
173
+ if (!isKeyMatcher(key)) return null;
174
+ const negated = key.startsWith("$pqkc-not:");
175
+ const prefix = negated ? "$pqkc-not:" : "$pqkc:";
176
+ const suffix = "$";
177
+ if (!key.endsWith(suffix)) return null;
178
+ const content = key.slice(prefix.length, -suffix.length);
179
+ if (content === "any") {
180
+ return { type: "any", negated };
181
+ }
182
+ if (content === "anyOther") {
183
+ return { type: "anyOther", negated };
184
+ }
185
+ if (content === "numeric") {
186
+ return { type: "numeric", negated };
187
+ }
188
+ if (content.startsWith("startingWith:")) {
189
+ return { type: "startingWith", parameter: content.slice("startingWith:".length), negated };
190
+ }
191
+ if (content.startsWith("endingWith:")) {
192
+ return { type: "endingWith", parameter: content.slice("endingWith:".length), negated };
193
+ }
194
+ if (content.startsWith("contains:")) {
195
+ return { type: "contains", parameter: content.slice("contains:".length), negated };
196
+ }
197
+ if (content.startsWith("matchesRegex:")) {
198
+ const regexStr = content.slice("matchesRegex:".length);
199
+ try {
200
+ if (regexStr.startsWith("/") && regexStr.lastIndexOf("/") > 0) {
201
+ const lastSlashIndex = regexStr.lastIndexOf("/");
202
+ const pattern = regexStr.slice(1, lastSlashIndex);
203
+ const flags = regexStr.slice(lastSlashIndex + 1);
204
+ return { type: "matchesRegex", parameter: new RegExp(pattern, flags), negated };
205
+ } else {
206
+ return { type: "matchesRegex", parameter: new RegExp(regexStr), negated };
207
+ }
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+ return null;
213
+ }
214
+ function keyMatchesPattern(key, matcher) {
215
+ let matches = false;
216
+ switch (matcher.type) {
217
+ case "any":
218
+ matches = true;
219
+ break;
220
+ case "anyOther":
221
+ matches = true;
222
+ break;
223
+ case "numeric":
224
+ matches = /^\d+$/.test(key);
225
+ break;
226
+ case "startingWith":
227
+ matches = key.startsWith(matcher.parameter);
228
+ break;
229
+ case "endingWith":
230
+ matches = key.endsWith(matcher.parameter);
231
+ break;
232
+ case "contains":
233
+ matches = key.includes(matcher.parameter);
234
+ break;
235
+ case "matchesRegex":
236
+ matches = matcher.parameter.test(key);
237
+ break;
238
+ }
239
+ return matcher.negated ? !matches : matches;
240
+ }
151
241
  function executeComparison(target, comparison, context) {
152
242
  const [type, value] = comparison;
153
243
  switch (type) {
@@ -844,7 +934,21 @@ function partialEqualInternal(target, sub, context) {
844
934
  return false;
845
935
  }
846
936
  let allMatch = true;
937
+ const regularKeys = [];
938
+ const keyMatchers = [];
847
939
  for (const key of Object.keys(sub)) {
940
+ if (isKeyMatcher(key)) {
941
+ const matcher = parseKeyMatcher(key);
942
+ if (matcher) {
943
+ keyMatchers.push({ key, matcher, value: sub[key] });
944
+ }
945
+ } else {
946
+ regularKeys.push(key);
947
+ }
948
+ }
949
+ const explicitlySpecifiedKeys = /* @__PURE__ */ new Set();
950
+ for (const key of regularKeys) {
951
+ explicitlySpecifiedKeys.add(key);
848
952
  if (isComparison(sub[key]) && sub[key]["~sc"][0] === "keyNotBePresent") {
849
953
  if (has.call(target, key)) {
850
954
  const oldPath2 = context.path;
@@ -888,6 +992,72 @@ function partialEqualInternal(target, sub, context) {
888
992
  context.path = oldPath;
889
993
  if (!result) allMatch = false;
890
994
  }
995
+ for (const { matcher, value } of keyMatchers) {
996
+ let foundMatch = false;
997
+ const matchedKeys = [];
998
+ if (matcher.type === "anyOther") {
999
+ for (const targetKey of Object.keys(target)) {
1000
+ if (!explicitlySpecifiedKeys.has(targetKey)) {
1001
+ const matches = keyMatchesPattern(targetKey, matcher);
1002
+ if (matches) {
1003
+ foundMatch = true;
1004
+ matchedKeys.push(targetKey);
1005
+ const oldPath = context.path;
1006
+ context.path = [...oldPath, targetKey];
1007
+ const result = partialEqualInternal(target[targetKey], value, context);
1008
+ context.path = oldPath;
1009
+ if (!result) allMatch = false;
1010
+ }
1011
+ }
1012
+ }
1013
+ } else if (matcher.type === "any") {
1014
+ let anyKeyMatched = false;
1015
+ for (const targetKey of Object.keys(target)) {
1016
+ if (keyMatchesPattern(targetKey, matcher)) {
1017
+ foundMatch = true;
1018
+ matchedKeys.push(targetKey);
1019
+ explicitlySpecifiedKeys.add(targetKey);
1020
+ const tempContext = {
1021
+ errors: [],
1022
+ path: [...context.path, targetKey]
1023
+ };
1024
+ const result = partialEqualInternal(target[targetKey], value, tempContext);
1025
+ if (result) {
1026
+ anyKeyMatched = true;
1027
+ break;
1028
+ }
1029
+ }
1030
+ }
1031
+ if (foundMatch && !anyKeyMatched) {
1032
+ addError(context, {
1033
+ message: `No keys matching pattern had the expected value`,
1034
+ received: { keysChecked: matchedKeys, availableKeys: Object.keys(target) }
1035
+ });
1036
+ allMatch = false;
1037
+ }
1038
+ } else {
1039
+ for (const targetKey of Object.keys(target)) {
1040
+ if (keyMatchesPattern(targetKey, matcher)) {
1041
+ foundMatch = true;
1042
+ matchedKeys.push(targetKey);
1043
+ explicitlySpecifiedKeys.add(targetKey);
1044
+ const oldPath = context.path;
1045
+ context.path = [...oldPath, targetKey];
1046
+ const result = partialEqualInternal(target[targetKey], value, context);
1047
+ context.path = oldPath;
1048
+ if (!result) allMatch = false;
1049
+ }
1050
+ }
1051
+ }
1052
+ if (!foundMatch) {
1053
+ const patternDescription = matcher.type === "anyOther" ? "anyOther (keys not explicitly specified)" : `${matcher.type}${matcher.parameter ? ` (${matcher.parameter})` : ""}`;
1054
+ addError(context, {
1055
+ message: `No keys found matching pattern: ${patternDescription}`,
1056
+ received: { availableKeys: Object.keys(target), explicitKeys: Array.from(explicitlySpecifiedKeys) }
1057
+ });
1058
+ allMatch = false;
1059
+ }
1060
+ }
891
1061
  return allMatch;
892
1062
  }
893
1063
  if (target !== sub) {
@@ -22,12 +22,24 @@ var stringUtils_exports = {};
22
22
  __export(stringUtils_exports, {
23
23
  concatStrings: () => concatStrings,
24
24
  convertToCamelCase: () => convertToCamelCase,
25
+ convertToConstantCase: () => convertToConstantCase,
26
+ convertToDotCase: () => convertToDotCase,
27
+ convertToKebabCase: () => convertToKebabCase,
25
28
  convertToPascalCase: () => convertToPascalCase,
29
+ convertToPathCase: () => convertToPathCase,
26
30
  convertToSentenceCase: () => convertToSentenceCase,
27
31
  convertToSnakeCase: () => convertToSnakeCase,
28
32
  convertToTitleCase: () => convertToTitleCase,
29
33
  formatNum: () => formatNum,
34
+ isCamelCase: () => isCamelCase,
35
+ isConstantCase: () => isConstantCase,
36
+ isDotCase: () => isDotCase,
37
+ isKebabCase: () => isKebabCase,
38
+ isPascalCase: () => isPascalCase,
39
+ isPathCase: () => isPathCase,
40
+ isSentenceCase: () => isSentenceCase,
30
41
  isSnakeCase: () => isSnakeCase,
42
+ isTitleCase: () => isTitleCase,
31
43
  joinStrings: () => joinStrings,
32
44
  removeANSIColors: () => removeANSIColors,
33
45
  truncateString: () => truncateString
@@ -56,6 +68,33 @@ function formatNum(num, maxDecimalsOrOptions = 2) {
56
68
  function isSnakeCase(str) {
57
69
  return /^[a-z0-9_]+$/.test(str);
58
70
  }
71
+ function isKebabCase(str) {
72
+ return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(str);
73
+ }
74
+ function isPascalCase(str) {
75
+ return /^[A-Z][a-zA-Z0-9]*$/.test(str);
76
+ }
77
+ function isCamelCase(str) {
78
+ return /^[a-z][a-zA-Z0-9]*$/.test(str);
79
+ }
80
+ function isTitleCase(str) {
81
+ return /^[A-Z][a-z0-9]*( ([A-Z][a-z0-9]*|[0-9]+))*$/.test(str);
82
+ }
83
+ function isSentenceCase(str) {
84
+ return /^[A-Z][a-z0-9]*( [a-z0-9]+)*$/.test(str);
85
+ }
86
+ function isConstantCase(str) {
87
+ return /^[A-Z_][A-Z0-9_]*$/.test(str);
88
+ }
89
+ function isDotCase(str) {
90
+ return /^[a-z0-9]+(\.[a-z0-9]+)*$/.test(str);
91
+ }
92
+ function isPathCase(str) {
93
+ return /^[a-z0-9]+(\/[a-z0-9]+)*$/.test(str);
94
+ }
95
+ function convertToKebabCase(str) {
96
+ return convertToSnakeCase(str).replace(/_/g, "-");
97
+ }
59
98
  function convertToSnakeCase(str) {
60
99
  return str.replace(/[\s\-.]+/g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9_]/g, "").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
61
100
  }
@@ -72,6 +111,15 @@ function convertToSentenceCase(str) {
72
111
  function convertToTitleCase(str) {
73
112
  return str.replace(/[\s\-.]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").split(/[\s_-]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
74
113
  }
114
+ function convertToConstantCase(str) {
115
+ return convertToSnakeCase(str).toUpperCase();
116
+ }
117
+ function convertToDotCase(str) {
118
+ return convertToSnakeCase(str).replace(/_/g, ".");
119
+ }
120
+ function convertToPathCase(str) {
121
+ return convertToSnakeCase(str).replace(/_/g, "/");
122
+ }
75
123
  function truncateString(str, length, ellipsis = "\u2026") {
76
124
  if (str.length <= length) return str;
77
125
  return str.slice(0, length - 1) + ellipsis;
@@ -83,12 +131,24 @@ function removeANSIColors(str) {
83
131
  0 && (module.exports = {
84
132
  concatStrings,
85
133
  convertToCamelCase,
134
+ convertToConstantCase,
135
+ convertToDotCase,
136
+ convertToKebabCase,
86
137
  convertToPascalCase,
138
+ convertToPathCase,
87
139
  convertToSentenceCase,
88
140
  convertToSnakeCase,
89
141
  convertToTitleCase,
90
142
  formatNum,
143
+ isCamelCase,
144
+ isConstantCase,
145
+ isDotCase,
146
+ isKebabCase,
147
+ isPascalCase,
148
+ isPathCase,
149
+ isSentenceCase,
91
150
  isSnakeCase,
151
+ isTitleCase,
92
152
  joinStrings,
93
153
  removeANSIColors,
94
154
  truncateString
@@ -14,12 +14,24 @@ declare function concatStrings(...args: (Arg | Arg[])[]): string;
14
14
  declare const joinStrings: typeof concatStrings;
15
15
  declare function formatNum(num: number, maxDecimalsOrOptions?: number | Intl.NumberFormatOptions): string;
16
16
  declare function isSnakeCase(str: string): boolean;
17
+ declare function isKebabCase(str: string): boolean;
18
+ declare function isPascalCase(str: string): boolean;
19
+ declare function isCamelCase(str: string): boolean;
20
+ declare function isTitleCase(str: string): boolean;
21
+ declare function isSentenceCase(str: string): boolean;
22
+ declare function isConstantCase(str: string): boolean;
23
+ declare function isDotCase(str: string): boolean;
24
+ declare function isPathCase(str: string): boolean;
25
+ declare function convertToKebabCase(str: string): string;
17
26
  declare function convertToSnakeCase(str: string): string;
18
27
  declare function convertToPascalCase(str: string): string;
19
28
  declare function convertToCamelCase(str: string): string;
20
29
  declare function convertToSentenceCase(str: string): string;
21
30
  declare function convertToTitleCase(str: string): string;
31
+ declare function convertToConstantCase(str: string): string;
32
+ declare function convertToDotCase(str: string): string;
33
+ declare function convertToPathCase(str: string): string;
22
34
  declare function truncateString(str: string, length: number, ellipsis?: string): string;
23
35
  declare function removeANSIColors(str: string): string;
24
36
 
25
- export { concatStrings, convertToCamelCase, convertToPascalCase, convertToSentenceCase, convertToSnakeCase, convertToTitleCase, formatNum, isSnakeCase, joinStrings, removeANSIColors, truncateString };
37
+ export { concatStrings, convertToCamelCase, convertToConstantCase, convertToDotCase, convertToKebabCase, convertToPascalCase, convertToPathCase, convertToSentenceCase, convertToSnakeCase, convertToTitleCase, formatNum, isCamelCase, isConstantCase, isDotCase, isKebabCase, isPascalCase, isPathCase, isSentenceCase, isSnakeCase, isTitleCase, joinStrings, removeANSIColors, truncateString };
@@ -14,12 +14,24 @@ declare function concatStrings(...args: (Arg | Arg[])[]): string;
14
14
  declare const joinStrings: typeof concatStrings;
15
15
  declare function formatNum(num: number, maxDecimalsOrOptions?: number | Intl.NumberFormatOptions): string;
16
16
  declare function isSnakeCase(str: string): boolean;
17
+ declare function isKebabCase(str: string): boolean;
18
+ declare function isPascalCase(str: string): boolean;
19
+ declare function isCamelCase(str: string): boolean;
20
+ declare function isTitleCase(str: string): boolean;
21
+ declare function isSentenceCase(str: string): boolean;
22
+ declare function isConstantCase(str: string): boolean;
23
+ declare function isDotCase(str: string): boolean;
24
+ declare function isPathCase(str: string): boolean;
25
+ declare function convertToKebabCase(str: string): string;
17
26
  declare function convertToSnakeCase(str: string): string;
18
27
  declare function convertToPascalCase(str: string): string;
19
28
  declare function convertToCamelCase(str: string): string;
20
29
  declare function convertToSentenceCase(str: string): string;
21
30
  declare function convertToTitleCase(str: string): string;
31
+ declare function convertToConstantCase(str: string): string;
32
+ declare function convertToDotCase(str: string): string;
33
+ declare function convertToPathCase(str: string): string;
22
34
  declare function truncateString(str: string, length: number, ellipsis?: string): string;
23
35
  declare function removeANSIColors(str: string): string;
24
36
 
25
- export { concatStrings, convertToCamelCase, convertToPascalCase, convertToSentenceCase, convertToSnakeCase, convertToTitleCase, formatNum, isSnakeCase, joinStrings, removeANSIColors, truncateString };
37
+ export { concatStrings, convertToCamelCase, convertToConstantCase, convertToDotCase, convertToKebabCase, convertToPascalCase, convertToPathCase, convertToSentenceCase, convertToSnakeCase, convertToTitleCase, formatNum, isCamelCase, isConstantCase, isDotCase, isKebabCase, isPascalCase, isPathCase, isSentenceCase, isSnakeCase, isTitleCase, joinStrings, removeANSIColors, truncateString };
@@ -1,25 +1,49 @@
1
1
  import {
2
2
  concatStrings,
3
3
  convertToCamelCase,
4
+ convertToConstantCase,
5
+ convertToDotCase,
6
+ convertToKebabCase,
4
7
  convertToPascalCase,
8
+ convertToPathCase,
5
9
  convertToSentenceCase,
6
10
  convertToSnakeCase,
7
11
  convertToTitleCase,
8
12
  formatNum,
13
+ isCamelCase,
14
+ isConstantCase,
15
+ isDotCase,
16
+ isKebabCase,
17
+ isPascalCase,
18
+ isPathCase,
19
+ isSentenceCase,
9
20
  isSnakeCase,
21
+ isTitleCase,
10
22
  joinStrings,
11
23
  removeANSIColors,
12
24
  truncateString
13
- } from "./chunk-B3KFV2MH.js";
25
+ } from "./chunk-BM4PYVOX.js";
14
26
  export {
15
27
  concatStrings,
16
28
  convertToCamelCase,
29
+ convertToConstantCase,
30
+ convertToDotCase,
31
+ convertToKebabCase,
17
32
  convertToPascalCase,
33
+ convertToPathCase,
18
34
  convertToSentenceCase,
19
35
  convertToSnakeCase,
20
36
  convertToTitleCase,
21
37
  formatNum,
38
+ isCamelCase,
39
+ isConstantCase,
40
+ isDotCase,
41
+ isKebabCase,
42
+ isPascalCase,
43
+ isPathCase,
44
+ isSentenceCase,
22
45
  isSnakeCase,
46
+ isTitleCase,
23
47
  joinStrings,
24
48
  removeANSIColors,
25
49
  truncateString
package/dist/testUtils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  yamlStringify
3
- } from "./chunk-7L4KCZJJ.js";
3
+ } from "./chunk-B6DNOZCP.js";
4
4
  import {
5
5
  omit,
6
6
  pick
@@ -22,7 +22,7 @@ import {
22
22
  import {
23
23
  clampMin
24
24
  } from "./chunk-HTCYUMDR.js";
25
- import "./chunk-B3KFV2MH.js";
25
+ import "./chunk-BM4PYVOX.js";
26
26
  import {
27
27
  arrayWithPrevAndIndex,
28
28
  filterAndMap
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  yamlStringify
3
- } from "./chunk-7L4KCZJJ.js";
3
+ } from "./chunk-B6DNOZCP.js";
4
4
  import "./chunk-IATIXMCE.js";
5
- import "./chunk-B3KFV2MH.js";
5
+ import "./chunk-BM4PYVOX.js";
6
6
  import "./chunk-JF2MDHOJ.js";
7
7
  export {
8
8
  yamlStringify
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.53.0",
4
+ "version": "3.55.0",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "dist",