@ls-stack/utils 3.46.0 → 3.48.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.
@@ -1,3 +1,5 @@
1
+ import { Result } from 't-result';
2
+
1
3
  type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsWith', value: string] | [
2
4
  type: 'hasType',
3
5
  value: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'function'
@@ -46,6 +48,13 @@ type Match = BaseMatch & {
46
48
  not: BaseMatch;
47
49
  };
48
50
  declare const match: Match;
51
+ type PartialError = {
52
+ path: string;
53
+ message: string;
54
+ received: any;
55
+ expected?: any;
56
+ };
57
+ declare function partialEqual(target: any, sub: any, returnErrors: true): Result<void, PartialError[]>;
49
58
  declare function partialEqual(target: any, sub: any): boolean;
50
59
 
51
60
  export { match, partialEqual };
@@ -3,6 +3,7 @@ import {
3
3
  } from "./chunk-JQFUKJU5.js";
4
4
 
5
5
  // src/partialEqual.ts
6
+ import { err, ok } from "t-result";
6
7
  var has = Object.prototype.hasOwnProperty;
7
8
  function createComparison(type) {
8
9
  return { "~sc": type };
@@ -302,7 +303,437 @@ function executeComparison(target, comparison) {
302
303
  return false;
303
304
  }
304
305
  }
305
- function partialEqual(target, sub) {
306
+ function formatPath(path) {
307
+ if (path.length === 0) return "";
308
+ let result = path[0] || "";
309
+ for (let i = 1; i < path.length; i++) {
310
+ const segment = path[i];
311
+ if (segment && segment.startsWith("[") && segment.endsWith("]")) {
312
+ result += segment;
313
+ } else if (segment) {
314
+ result += `.${segment}`;
315
+ }
316
+ }
317
+ return result;
318
+ }
319
+ function addError(context, message, received, expected) {
320
+ const error = {
321
+ path: formatPath(context.path),
322
+ message,
323
+ received
324
+ };
325
+ if (expected !== void 0) {
326
+ error.expected = expected;
327
+ }
328
+ context.errors.push(error);
329
+ }
330
+ function executeComparisonWithErrorCollection(target, comparison, context) {
331
+ const [type, value] = comparison;
332
+ switch (type) {
333
+ case "hasType":
334
+ switch (value) {
335
+ case "string":
336
+ if (typeof target !== "string") {
337
+ addError(context, `Expected type string`, target);
338
+ }
339
+ break;
340
+ case "number":
341
+ if (typeof target !== "number") {
342
+ addError(context, `Expected type number`, target);
343
+ }
344
+ break;
345
+ case "boolean":
346
+ if (typeof target !== "boolean") {
347
+ addError(context, `Expected type boolean`, target);
348
+ }
349
+ break;
350
+ case "function":
351
+ if (typeof target !== "function") {
352
+ addError(context, `Expected type function`, target);
353
+ }
354
+ break;
355
+ case "array":
356
+ if (!Array.isArray(target)) {
357
+ addError(context, `Expected array`, target);
358
+ }
359
+ break;
360
+ case "object":
361
+ if (typeof target !== "object" || target === null || Array.isArray(target)) {
362
+ addError(context, `Expected object`, target);
363
+ }
364
+ break;
365
+ }
366
+ break;
367
+ case "isInstanceOf":
368
+ if (!(target instanceof value)) {
369
+ addError(
370
+ context,
371
+ `Expected instance of ${value.name}`,
372
+ target
373
+ );
374
+ }
375
+ break;
376
+ case "strStartsWith":
377
+ if (typeof target !== "string" || !target.startsWith(value)) {
378
+ addError(
379
+ context,
380
+ `Expected string starting with "${value}"`,
381
+ target
382
+ );
383
+ }
384
+ break;
385
+ case "strEndsWith":
386
+ if (typeof target !== "string" || !target.endsWith(value)) {
387
+ addError(
388
+ context,
389
+ `Expected string ending with "${value}"`,
390
+ target
391
+ );
392
+ }
393
+ break;
394
+ case "strContains":
395
+ if (typeof target !== "string" || !target.includes(value)) {
396
+ addError(
397
+ context,
398
+ `Expected string containing "${value}"`,
399
+ target
400
+ );
401
+ }
402
+ break;
403
+ case "strMatchesRegex":
404
+ if (typeof target !== "string" || !value.test(target)) {
405
+ addError(
406
+ context,
407
+ `Expected string matching regex ${value}`,
408
+ target
409
+ );
410
+ }
411
+ break;
412
+ case "numIsGreaterThan":
413
+ if (typeof target !== "number" || target <= value) {
414
+ addError(
415
+ context,
416
+ `Expected number greater than ${value}`,
417
+ target
418
+ );
419
+ }
420
+ break;
421
+ case "numIsGreaterThanOrEqual":
422
+ if (typeof target !== "number" || target < value) {
423
+ addError(
424
+ context,
425
+ `Expected number greater than or equal to ${value}`,
426
+ target
427
+ );
428
+ }
429
+ break;
430
+ case "numIsLessThan":
431
+ if (typeof target !== "number" || target >= value) {
432
+ addError(
433
+ context,
434
+ `Expected number less than ${value}`,
435
+ target
436
+ );
437
+ }
438
+ break;
439
+ case "numIsLessThanOrEqual":
440
+ if (typeof target !== "number" || target > value) {
441
+ addError(
442
+ context,
443
+ `Expected number less than or equal to ${value}`,
444
+ target
445
+ );
446
+ }
447
+ break;
448
+ case "numIsInRange":
449
+ if (typeof target !== "number" || target < value[0] || target > value[1]) {
450
+ addError(
451
+ context,
452
+ `Expected number in range [${value[0]}, ${value[1]}]`,
453
+ target
454
+ );
455
+ }
456
+ break;
457
+ case "jsonStringHasPartial":
458
+ if (typeof target !== "string") {
459
+ addError(context, `Expected JSON string`, target);
460
+ } else {
461
+ try {
462
+ const parsed = JSON.parse(target);
463
+ partialEqualWithErrorCollection(parsed, value, context);
464
+ } catch {
465
+ addError(
466
+ context,
467
+ `Expected valid JSON string`,
468
+ target
469
+ );
470
+ }
471
+ }
472
+ break;
473
+ case "deepEqual":
474
+ if (!deepEqual(target, value)) {
475
+ addError(context, `Expected deep equal`, target, value);
476
+ }
477
+ break;
478
+ case "partialEqual":
479
+ partialEqualWithErrorCollection(target, value, context);
480
+ break;
481
+ case "custom":
482
+ if (!value(target)) {
483
+ addError(context, `Custom validation failed`, target, "valid value");
484
+ }
485
+ break;
486
+ case "keyNotBePresent":
487
+ addError(context, `Key should not be present`, target);
488
+ break;
489
+ case "any": {
490
+ for (const comp of value) {
491
+ const subContext = {
492
+ errors: [],
493
+ path: [...context.path]
494
+ };
495
+ executeComparisonWithErrorCollection(target, comp, subContext);
496
+ if (subContext.errors.length === 0) {
497
+ return;
498
+ }
499
+ }
500
+ addError(
501
+ context,
502
+ `None of the alternative comparisons matched`,
503
+ target
504
+ );
505
+ break;
506
+ }
507
+ case "all":
508
+ for (const comp of value) {
509
+ executeComparisonWithErrorCollection(target, comp, context);
510
+ }
511
+ break;
512
+ case "not": {
513
+ const subContext = {
514
+ errors: [],
515
+ path: [...context.path]
516
+ };
517
+ executeComparisonWithErrorCollection(target, value, subContext);
518
+ if (subContext.errors.length === 0) {
519
+ addError(
520
+ context,
521
+ `Expected negated condition to fail`,
522
+ target
523
+ );
524
+ }
525
+ break;
526
+ }
527
+ case "withNoExtraKeys":
528
+ case "withDeepNoExtraKeys":
529
+ case "noExtraDefinedKeys":
530
+ case "deepNoExtraDefinedKeys":
531
+ addError(
532
+ context,
533
+ `Complex validation not supported in error collection mode yet`,
534
+ target
535
+ );
536
+ break;
537
+ }
538
+ }
539
+ function executeComparisonWithKeyContextAndErrorCollection(target, comp, keyExists, context) {
540
+ const [type, value] = comp;
541
+ if (type === "keyNotBePresent") {
542
+ if (keyExists) {
543
+ addError(context, `Key should not be present`, target);
544
+ }
545
+ return;
546
+ }
547
+ if (type === "any") {
548
+ for (const childComp of value) {
549
+ const subContext = {
550
+ errors: [],
551
+ path: [...context.path]
552
+ };
553
+ executeComparisonWithKeyContextAndErrorCollection(
554
+ target,
555
+ childComp,
556
+ keyExists,
557
+ subContext
558
+ );
559
+ if (subContext.errors.length === 0) {
560
+ return;
561
+ }
562
+ }
563
+ addError(
564
+ context,
565
+ `None of the alternative comparisons matched`,
566
+ target,
567
+ "any alternative match"
568
+ );
569
+ return;
570
+ }
571
+ if (type === "not") {
572
+ const subContext = {
573
+ errors: [],
574
+ path: [...context.path]
575
+ };
576
+ executeComparisonWithKeyContextAndErrorCollection(
577
+ target,
578
+ value,
579
+ keyExists,
580
+ subContext
581
+ );
582
+ if (subContext.errors.length === 0) {
583
+ addError(
584
+ context,
585
+ `Expected negated condition to fail`,
586
+ target,
587
+ "negated condition"
588
+ );
589
+ }
590
+ return;
591
+ }
592
+ executeComparisonWithErrorCollection(target, comp, context);
593
+ }
594
+ function partialEqualWithErrorCollection(target, sub, context) {
595
+ if (sub === target) return;
596
+ if (sub && typeof sub === "object" && "~sc" in sub) {
597
+ executeComparisonWithErrorCollection(target, sub["~sc"], context);
598
+ return;
599
+ }
600
+ if (sub && target && sub.constructor === target.constructor) {
601
+ const ctor = sub.constructor;
602
+ if (ctor === Date) {
603
+ if (sub.getTime() !== target.getTime()) {
604
+ addError(context, `Date mismatch`, target, sub);
605
+ }
606
+ return;
607
+ }
608
+ if (ctor === RegExp) {
609
+ if (sub.toString() !== target.toString()) {
610
+ addError(context, `RegExp mismatch`, target, sub);
611
+ }
612
+ return;
613
+ }
614
+ if (ctor === Array) {
615
+ if (sub.length > target.length) {
616
+ addError(
617
+ context,
618
+ `Array too short: expected at least ${sub.length} elements, got ${target.length}`,
619
+ target,
620
+ sub
621
+ );
622
+ return;
623
+ }
624
+ for (let i = 0; i < sub.length; i++) {
625
+ context.path.push(`[${i}]`);
626
+ partialEqualWithErrorCollection(target[i], sub[i], context);
627
+ context.path.pop();
628
+ }
629
+ return;
630
+ }
631
+ if (ctor === Set) {
632
+ if (sub.size > target.size) {
633
+ addError(
634
+ context,
635
+ `Set too small: expected at least ${sub.size} elements, got ${target.size}`,
636
+ target,
637
+ sub
638
+ );
639
+ return;
640
+ }
641
+ for (const value of sub) {
642
+ let found = false;
643
+ if (value && typeof value === "object") {
644
+ found = !!find(target, value);
645
+ } else {
646
+ found = target.has(value);
647
+ }
648
+ if (!found) {
649
+ addError(context, `Set missing value`, target, value);
650
+ }
651
+ }
652
+ return;
653
+ }
654
+ if (ctor === Map) {
655
+ if (sub.size > target.size) {
656
+ addError(
657
+ context,
658
+ `Map too small: expected at least ${sub.size} entries, got ${target.size}`,
659
+ target,
660
+ sub
661
+ );
662
+ return;
663
+ }
664
+ for (const [key, value] of sub) {
665
+ let targetKey = key;
666
+ if (key && typeof key === "object") {
667
+ targetKey = find(target, key);
668
+ if (!targetKey) {
669
+ addError(context, `Map missing key`, target, key);
670
+ continue;
671
+ }
672
+ }
673
+ if (!target.has(targetKey)) {
674
+ addError(context, `Map missing key`, target, key);
675
+ continue;
676
+ }
677
+ context.path.push(`[${key}]`);
678
+ partialEqualWithErrorCollection(target.get(targetKey), value, context);
679
+ context.path.pop();
680
+ }
681
+ return;
682
+ }
683
+ if (!ctor || typeof sub === "object") {
684
+ for (const key in sub) {
685
+ if (has.call(sub, key)) {
686
+ const subValue = sub[key];
687
+ context.path.push(key);
688
+ if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "keyNotBePresent") {
689
+ if (has.call(target, key)) {
690
+ addError(
691
+ context,
692
+ `Key should not be present`,
693
+ target[key],
694
+ "key not present"
695
+ );
696
+ }
697
+ } else if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "any") {
698
+ const targetHasKey = has.call(target, key);
699
+ const targetValue = targetHasKey ? target[key] : void 0;
700
+ executeComparisonWithKeyContextAndErrorCollection(
701
+ targetValue,
702
+ subValue["~sc"],
703
+ targetHasKey,
704
+ context
705
+ );
706
+ } else {
707
+ if (!has.call(target, key)) {
708
+ addError(context, `Missing property`, void 0, subValue);
709
+ } else {
710
+ partialEqualWithErrorCollection(target[key], subValue, context);
711
+ }
712
+ }
713
+ context.path.pop();
714
+ }
715
+ }
716
+ return;
717
+ }
718
+ }
719
+ if (sub !== sub && target !== target) {
720
+ return;
721
+ }
722
+ addError(context, `Value mismatch`, target, sub);
723
+ }
724
+ function partialEqual(target, sub, returnErrors) {
725
+ if (returnErrors === true) {
726
+ const context = {
727
+ errors: [],
728
+ path: []
729
+ };
730
+ partialEqualWithErrorCollection(target, sub, context);
731
+ if (context.errors.length === 0) {
732
+ return ok(void 0);
733
+ } else {
734
+ return err(context.errors);
735
+ }
736
+ }
306
737
  if (sub === target) return true;
307
738
  if (sub && typeof sub === "object" && "~sc" in sub) {
308
739
  return executeComparison(target, sub["~sc"]);
@@ -155,6 +155,51 @@ function deepEqual(foo, bar, maxDepth = 20) {
155
155
  return foo !== foo && bar !== bar;
156
156
  }
157
157
 
158
+ // src/deepReplaceValues.ts
159
+ function applyValueReplacements(value, replaceValues, visited, currentPath) {
160
+ function processValue(val, path) {
161
+ const replacement = replaceValues(val, path);
162
+ if (replacement !== false) {
163
+ return replacement.newValue;
164
+ }
165
+ if (Array.isArray(val)) {
166
+ if (visited.has(val)) {
167
+ throw new Error("Circular reference detected in array");
168
+ }
169
+ visited.add(val);
170
+ try {
171
+ return val.map((item, index) => {
172
+ const itemPath = path ? `${path}[${index}]` : `[${index}]`;
173
+ return processValue(item, itemPath);
174
+ });
175
+ } finally {
176
+ visited.delete(val);
177
+ }
178
+ }
179
+ if (isPlainObject(val)) {
180
+ if (visited.has(val)) {
181
+ throw new Error("Circular reference detected in object");
182
+ }
183
+ visited.add(val);
184
+ try {
185
+ const result = {};
186
+ for (const [key, itemValue] of Object.entries(val)) {
187
+ const itemPath = path ? `${path}.${key}` : key;
188
+ result[key] = processValue(itemValue, itemPath);
189
+ }
190
+ return result;
191
+ } finally {
192
+ visited.delete(val);
193
+ }
194
+ }
195
+ return val;
196
+ }
197
+ return processValue(value, currentPath);
198
+ }
199
+ function deepReplaceValues(value, replaceValues) {
200
+ return applyValueReplacements(value, replaceValues, /* @__PURE__ */ new Set(), "");
201
+ }
202
+
158
203
  // src/filterObjectOrArrayKeys.ts
159
204
  var ID_PROP_REGEXP = /^(id_|key_|id-|key-)|(_id|_key|-id|-key)$/i;
160
205
  function filterObjectOrArrayKeys(objOrArray, {
@@ -1368,7 +1413,7 @@ function compactSnapshot(value, {
1368
1413
  }
1369
1414
  }
1370
1415
  if (replaceValues) {
1371
- processedValue = applyValueReplacements(processedValue, replaceValues);
1416
+ processedValue = deepReplaceValues(processedValue, replaceValues);
1372
1417
  }
1373
1418
  processedValue = showBooleansAs ? replaceBooleansWithEmoji(processedValue, showBooleansAs) : processedValue;
1374
1419
  return `
@@ -1379,46 +1424,6 @@ ${yamlStringify(processedValue, {
1379
1424
  ...options
1380
1425
  })}`;
1381
1426
  }
1382
- function applyValueReplacements(value, replaceValues, visited = /* @__PURE__ */ new Set(), currentPath = "") {
1383
- function processValue(val, path) {
1384
- const replacement = replaceValues(val, path);
1385
- if (replacement !== false) {
1386
- return replacement.newValue;
1387
- }
1388
- if (Array.isArray(val)) {
1389
- if (visited.has(val)) {
1390
- throw new Error("Circular reference detected in array");
1391
- }
1392
- visited.add(val);
1393
- try {
1394
- return val.map((item, index) => {
1395
- const itemPath = path ? `${path}[${index}]` : `[${index}]`;
1396
- return processValue(item, itemPath);
1397
- });
1398
- } finally {
1399
- visited.delete(val);
1400
- }
1401
- }
1402
- if (isPlainObject(val)) {
1403
- if (visited.has(val)) {
1404
- throw new Error("Circular reference detected in object");
1405
- }
1406
- visited.add(val);
1407
- try {
1408
- const result = {};
1409
- for (const [key, itemValue] of Object.entries(val)) {
1410
- const itemPath = path ? `${path}.${key}` : key;
1411
- result[key] = processValue(itemValue, itemPath);
1412
- }
1413
- return result;
1414
- } finally {
1415
- visited.delete(val);
1416
- }
1417
- }
1418
- return val;
1419
- }
1420
- return processValue(value, currentPath);
1421
- }
1422
1427
  function replaceBooleansWithEmoji(value, showBooleansAs, visited = /* @__PURE__ */ new Set()) {
1423
1428
  if (showBooleansAs === false) {
1424
1429
  return value;
package/dist/testUtils.js CHANGED
@@ -6,13 +6,16 @@ import {
6
6
  pick
7
7
  } from "./chunk-Y45CE75W.js";
8
8
  import "./chunk-GMJTLFM6.js";
9
+ import {
10
+ filterObjectOrArrayKeys
11
+ } from "./chunk-6CG6JZKB.js";
9
12
  import "./chunk-IATIXMCE.js";
10
13
  import {
11
14
  deepEqual
12
15
  } from "./chunk-JQFUKJU5.js";
13
16
  import {
14
- filterObjectOrArrayKeys
15
- } from "./chunk-6CG6JZKB.js";
17
+ deepReplaceValues
18
+ } from "./chunk-PUKVXYYL.js";
16
19
  import {
17
20
  defer
18
21
  } from "./chunk-DFXNVEH6.js";
@@ -281,7 +284,7 @@ function compactSnapshot(value, {
281
284
  }
282
285
  }
283
286
  if (replaceValues) {
284
- processedValue = applyValueReplacements(processedValue, replaceValues);
287
+ processedValue = deepReplaceValues(processedValue, replaceValues);
285
288
  }
286
289
  processedValue = showBooleansAs ? replaceBooleansWithEmoji(processedValue, showBooleansAs) : processedValue;
287
290
  return `
@@ -292,46 +295,6 @@ ${yamlStringify(processedValue, {
292
295
  ...options
293
296
  })}`;
294
297
  }
295
- function applyValueReplacements(value, replaceValues, visited = /* @__PURE__ */ new Set(), currentPath = "") {
296
- function processValue(val, path) {
297
- const replacement = replaceValues(val, path);
298
- if (replacement !== false) {
299
- return replacement.newValue;
300
- }
301
- if (Array.isArray(val)) {
302
- if (visited.has(val)) {
303
- throw new Error("Circular reference detected in array");
304
- }
305
- visited.add(val);
306
- try {
307
- return val.map((item, index) => {
308
- const itemPath = path ? `${path}[${index}]` : `[${index}]`;
309
- return processValue(item, itemPath);
310
- });
311
- } finally {
312
- visited.delete(val);
313
- }
314
- }
315
- if (isPlainObject(val)) {
316
- if (visited.has(val)) {
317
- throw new Error("Circular reference detected in object");
318
- }
319
- visited.add(val);
320
- try {
321
- const result = {};
322
- for (const [key, itemValue] of Object.entries(val)) {
323
- const itemPath = path ? `${path}.${key}` : key;
324
- result[key] = processValue(itemValue, itemPath);
325
- }
326
- return result;
327
- } finally {
328
- visited.delete(val);
329
- }
330
- }
331
- return val;
332
- }
333
- return processValue(value, currentPath);
334
- }
335
298
  function replaceBooleansWithEmoji(value, showBooleansAs, visited = /* @__PURE__ */ new Set()) {
336
299
  if (showBooleansAs === false) {
337
300
  return value;
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.46.0",
4
+ "version": "3.48.0",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "dist",
@@ -76,6 +76,10 @@
76
76
  "import": "./dist/deepEqual.js",
77
77
  "require": "./dist/deepEqual.cjs"
78
78
  },
79
+ "./deepReplaceValues": {
80
+ "import": "./dist/deepReplaceValues.js",
81
+ "require": "./dist/deepReplaceValues.cjs"
82
+ },
79
83
  "./enhancedMap": {
80
84
  "import": "./dist/enhancedMap.js",
81
85
  "require": "./dist/enhancedMap.cjs"