@develia/commons 0.4.26 → 0.6.1

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.
Files changed (114) hide show
  1. package/dist/array.d.ts +10 -0
  2. package/dist/array.d.ts.map +1 -1
  3. package/dist/array.js +18 -0
  4. package/dist/array.js.map +1 -1
  5. package/dist/async-pool-executor.d.ts +3 -5
  6. package/dist/async-pool-executor.d.ts.map +1 -1
  7. package/dist/async-pool-executor.js +9 -9
  8. package/dist/async-pool-executor.js.map +1 -1
  9. package/dist/browser/form-utilities.d.ts +0 -2
  10. package/dist/browser/form-utilities.d.ts.map +1 -1
  11. package/dist/browser/form-utilities.js +3 -23
  12. package/dist/browser/form-utilities.js.map +1 -1
  13. package/dist/browser/geo-location.js +3 -3
  14. package/dist/browser/geo-location.js.map +1 -1
  15. package/dist/browser/native-bridge.d.ts +1 -1
  16. package/dist/browser/native-bridge.d.ts.map +1 -1
  17. package/dist/browser/native-bridge.js +2 -2
  18. package/dist/browser/native-bridge.js.map +1 -1
  19. package/dist/cancellable-promise.d.ts +8 -2
  20. package/dist/cancellable-promise.d.ts.map +1 -1
  21. package/dist/cancellable-promise.js +9 -3
  22. package/dist/cancellable-promise.js.map +1 -1
  23. package/dist/clean-url-search-params.js +3 -3
  24. package/dist/clean-url-search-params.js.map +1 -1
  25. package/dist/collections/from.d.ts +16 -5
  26. package/dist/collections/from.d.ts.map +1 -1
  27. package/dist/collections/from.js +69 -38
  28. package/dist/collections/from.js.map +1 -1
  29. package/dist/draft.d.ts +2 -2
  30. package/dist/draft.d.ts.map +1 -1
  31. package/dist/draft.js +2 -2
  32. package/dist/draft.js.map +1 -1
  33. package/dist/functions.d.ts +2 -2
  34. package/dist/functions.d.ts.map +1 -1
  35. package/dist/functions.js +1 -1
  36. package/dist/functions.js.map +1 -1
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/math.d.ts +14 -5
  42. package/dist/math.d.ts.map +1 -1
  43. package/dist/math.js +16 -5
  44. package/dist/math.js.map +1 -1
  45. package/dist/parse.d.ts +62 -44
  46. package/dist/parse.d.ts.map +1 -1
  47. package/dist/parse.js +95 -59
  48. package/dist/parse.js.map +1 -1
  49. package/dist/range.d.ts +2 -10
  50. package/dist/range.d.ts.map +1 -1
  51. package/dist/range.js +0 -12
  52. package/dist/range.js.map +1 -1
  53. package/dist/stopwatch.d.ts +2 -2
  54. package/dist/stopwatch.d.ts.map +1 -1
  55. package/dist/stopwatch.js +9 -9
  56. package/dist/stopwatch.js.map +1 -1
  57. package/dist/string.d.ts +15 -0
  58. package/dist/string.d.ts.map +1 -1
  59. package/dist/string.js +28 -3
  60. package/dist/string.js.map +1 -1
  61. package/dist/timespan.d.ts +10 -15
  62. package/dist/timespan.d.ts.map +1 -1
  63. package/dist/timespan.js +10 -15
  64. package/dist/timespan.js.map +1 -1
  65. package/dist/triggers.d.ts +6 -15
  66. package/dist/triggers.d.ts.map +1 -1
  67. package/dist/triggers.js +8 -22
  68. package/dist/triggers.js.map +1 -1
  69. package/dist/types.d.ts +0 -1
  70. package/dist/types.d.ts.map +1 -1
  71. package/dist/types.js.map +1 -1
  72. package/dist/utilities.d.ts +7 -6
  73. package/dist/utilities.d.ts.map +1 -1
  74. package/dist/utilities.js +19 -20
  75. package/dist/utilities.js.map +1 -1
  76. package/dist/uuid.d.ts +73 -0
  77. package/dist/uuid.d.ts.map +1 -0
  78. package/dist/uuid.js +129 -0
  79. package/dist/uuid.js.map +1 -0
  80. package/package.json +5 -1
  81. package/src/array.ts +19 -0
  82. package/src/async-pool-executor.ts +9 -9
  83. package/src/browser/form-utilities.ts +3 -32
  84. package/src/browser/geo-location.ts +3 -3
  85. package/src/browser/index.ts +0 -0
  86. package/src/browser/native-bridge.ts +2 -2
  87. package/src/cancellable-promise.ts +9 -3
  88. package/src/clean-url-search-params.ts +3 -3
  89. package/src/collections/cache-dictionary.ts +0 -0
  90. package/src/collections/default-map.ts +0 -0
  91. package/src/collections/default-weak-map.ts +0 -0
  92. package/src/collections/from.ts +70 -38
  93. package/src/collections/index.ts +0 -0
  94. package/src/draft.ts +3 -3
  95. package/src/event-bus.ts +0 -0
  96. package/src/functions.ts +3 -3
  97. package/src/index.ts +2 -1
  98. package/src/key-value-pair.ts +0 -0
  99. package/src/lazy.ts +0 -0
  100. package/src/math.ts +17 -5
  101. package/src/order.ts +0 -0
  102. package/src/parse.ts +108 -66
  103. package/src/patch.ts +0 -0
  104. package/src/range.ts +2 -16
  105. package/src/stopwatch.ts +9 -9
  106. package/src/string.ts +33 -3
  107. package/src/timer.ts +0 -0
  108. package/src/timespan.ts +10 -15
  109. package/src/triggers.ts +10 -24
  110. package/src/type-code.ts +0 -0
  111. package/src/type.ts +0 -0
  112. package/src/types.ts +0 -1
  113. package/src/utilities.ts +22 -25
  114. package/src/uuid.ts +153 -0
@@ -7,12 +7,12 @@ function isRecordParams(params: unknown): params is Record<string, any> {
7
7
  typeof (params as any)[Symbol.iterator] !== 'function';
8
8
  }
9
9
 
10
- function appendCleanValue(params: URLSearchParams, key: string, value: any): void {
10
+ function appendNonNullishValues(params: URLSearchParams, key: string, value: any): void {
11
11
  if (value === null || value === undefined) return;
12
12
 
13
13
  if (Array.isArray(value)) {
14
14
  for (const item of value) {
15
- appendCleanValue(params, key, item);
15
+ appendNonNullishValues(params, key, item);
16
16
  }
17
17
  return;
18
18
  }
@@ -31,7 +31,7 @@ export default class CleanURLSearchParams extends URLSearchParams {
31
31
 
32
32
  // Filtrar `null` y `undefined` antes de añadirlos a URLSearchParams
33
33
  for (const [key, value] of Object.entries(params)) {
34
- appendCleanValue(this, key, value);
34
+ appendNonNullishValues(this, key, value);
35
35
  }
36
36
  }
37
37
  }
File without changes
File without changes
File without changes
@@ -485,7 +485,7 @@ export class From<T> implements Iterable<T> {
485
485
  array.sort((a, b) => {
486
486
  const aValue = mapper(a);
487
487
  const bValue = mapper(b);
488
- return aValue > bValue ? 1 : bValue < aValue ? -1 : 0;
488
+ return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
489
489
  });
490
490
  } else {
491
491
  throw new Error("invalid order argument");
@@ -713,11 +713,11 @@ export class From<T> implements Iterable<T> {
713
713
 
714
714
 
715
715
  /**
716
- * Concatenates two sequences.
716
+ * The elements of this sequence followed by those of the other, duplicates included, as LINQ's Concat.
717
717
  * @param {Iterable<T>} other - The other sequence.
718
718
  * @returns {From<T>} A new From instance.
719
719
  */
720
- public union(other: Iterable<T>): From<T> {
720
+ public concat(other: Iterable<T>): From<T> {
721
721
  const self = this;
722
722
  return From.fn<T>(function* () {
723
723
  yield* self;
@@ -726,7 +726,19 @@ export class From<T> implements Iterable<T> {
726
726
  }
727
727
 
728
728
  /**
729
- * Correlates the elements of two sequences based on matching keys.
729
+ * The set union, as LINQ's Union: the elements of this sequence and then those of the other, each one once (the
730
+ * first time it appears), compared as distinct() does.
731
+ * @param {Iterable<T>} other - The other sequence.
732
+ * @param {EqualityComparer<T>} [equalityComparer] - Optional equality comparer; shallowEqual when not given.
733
+ * @returns {From<T>} A new From instance.
734
+ */
735
+ public union(other: Iterable<T>, equalityComparer?: EqualityComparer<T>): From<T> {
736
+ return this.concat(other).distinct(equalityComparer);
737
+ }
738
+
739
+ /**
740
+ * Correlates the elements of two sequences based on matching keys: one result per matching pair, so an element
741
+ * that matches several elements of the other sequence gives one result for each.
730
742
  * @template T2, R, R2
731
743
  * @param {Iterable<T2>} other - The other sequence.
732
744
  * @param {UnaryFunction<T, R>} thisKeySelector - Key selector for this sequence.
@@ -738,18 +750,20 @@ export class From<T> implements Iterable<T> {
738
750
 
739
751
  const self = this;
740
752
  return From.fn<R2>(() => {
741
- const otherByKey = new Map<R, T2>();
742
- for (let item of other) {
743
- otherByKey.set(otherKeySelector(item), item);
753
+ const otherByKey = groupItemsByKey(other, otherKeySelector);
754
+ const results: R2[] = [];
755
+ for (const item of self) {
756
+ for (const otherItem of otherByKey.get(thisKeySelector(item)) ?? []) {
757
+ results.push(resultSelector(item, otherItem));
758
+ }
744
759
  }
745
- return Array.from(self)
746
- .filter(item => otherByKey.has(thisKeySelector(item)))
747
- .map(item => resultSelector(item, otherByKey.get(thisKeySelector(item))!));
760
+ return results;
748
761
  });
749
762
  }
750
763
 
751
764
  /**
752
- * Performs a left join on two sequences.
765
+ * Performs a left join on two sequences: one result per matching pair, and one with `undefined` for each element of
766
+ * this sequence that matches none.
753
767
  * @template U, K, R
754
768
  * @param {Iterable<U>} other - The other sequence.
755
769
  * @param {UnaryFunction<T, K>} thisKeySelector - Key selector for this sequence.
@@ -761,11 +775,19 @@ export class From<T> implements Iterable<T> {
761
775
 
762
776
  const self = this;
763
777
  return From.fn<R>(() => {
764
- const otherByKey = new Map<K, U>();
765
- for (let item of other) {
766
- otherByKey.set(otherKeySelector(item), item);
778
+ const otherByKey = groupItemsByKey(other, otherKeySelector);
779
+ const results: R[] = [];
780
+ for (const item of self) {
781
+ const matches = otherByKey.get(thisKeySelector(item));
782
+ if (matches === undefined) {
783
+ results.push(resultSelector(item, undefined));
784
+ continue;
785
+ }
786
+ for (const otherItem of matches) {
787
+ results.push(resultSelector(item, otherItem));
788
+ }
767
789
  }
768
- return Array.from(self).map(item => resultSelector(item, otherByKey.get(thisKeySelector(item))));
790
+ return results;
769
791
  });
770
792
  }
771
793
 
@@ -783,15 +805,8 @@ export class From<T> implements Iterable<T> {
783
805
 
784
806
  const self = this;
785
807
  return From.fn<R>(() => {
786
- const otherByKeys = new Map<K, Array<U>>();
787
- for (let item of other) {
788
- const key = otherKeySelector(item);
789
- if (!otherByKeys.has(key)) {
790
- otherByKeys.set(key, []);
791
- }
792
- otherByKeys.get(key)!.push(item);
793
- }
794
- return Array.from(self).map(item => resultSelector(item, otherByKeys.get(thisKeySelector(item)) || []));
808
+ const otherByKey = groupItemsByKey(other, otherKeySelector);
809
+ return Array.from(self).map(item => resultSelector(item, otherByKey.get(thisKeySelector(item)) ?? []));
795
810
  });
796
811
  }
797
812
 
@@ -808,21 +823,15 @@ export class From<T> implements Iterable<T> {
808
823
 
809
824
  const self = this;
810
825
  return From.fn<R>(() => {
811
- const thisByKeys = new Map<K, Array<T>>();
812
- for (let item of self) {
813
- const key = thisKeySelector(item);
814
- if (!thisByKeys.has(key)) {
815
- thisByKeys.set(key, []);
816
- }
817
- thisByKeys.get(key)!.push(item);
818
- }
819
- return Array.from(other).map(item => resultSelector(thisByKeys.get(otherKeySelector(item)) || [], item));
826
+ const thisByKey = groupItemsByKey(self, thisKeySelector);
827
+ return Array.from(other).map(item => resultSelector(thisByKey.get(otherKeySelector(item)) ?? [], item));
820
828
  });
821
829
  }
822
830
 
823
831
 
824
832
  /**
825
- * Performs a right join on two sequences.
833
+ * Performs a right join on two sequences: one result per matching pair, and one with `undefined` for each element of
834
+ * the other sequence that matches none.
826
835
  * @template U, K, R
827
836
  * @param {Iterable<U>} other - The other sequence.
828
837
  * @param {UnaryFunction<T, K>} thisKeySelector - Key selector for this sequence.
@@ -834,16 +843,39 @@ export class From<T> implements Iterable<T> {
834
843
 
835
844
  const self = this;
836
845
  return From.fn<R>(() => {
837
- const thisByKey = new Map<K, T>();
838
- for (let item of self) {
839
- thisByKey.set(thisKeySelector(item), item);
846
+ const thisByKey = groupItemsByKey(self, thisKeySelector);
847
+ const results: R[] = [];
848
+ for (const otherItem of other) {
849
+ const matches = thisByKey.get(otherKeySelector(otherItem));
850
+ if (matches === undefined) {
851
+ results.push(resultSelector(undefined, otherItem));
852
+ continue;
853
+ }
854
+ for (const item of matches) {
855
+ results.push(resultSelector(item, otherItem));
856
+ }
840
857
  }
841
- return Array.from(other).map(item => resultSelector(thisByKey.get(otherKeySelector(item)), item));
858
+ return results;
842
859
  });
843
860
  }
844
861
 
845
862
  }
846
863
 
864
+ /** The items of a sequence in lists by their key, in their order, for the joins. */
865
+ function groupItemsByKey<TItem, TKey>(items: Iterable<TItem>, keySelector: UnaryFunction<TItem, TKey>): Map<TKey, TItem[]> {
866
+ const itemsByKey = new Map<TKey, TItem[]>();
867
+ for (const item of items) {
868
+ const key = keySelector(item);
869
+ const group = itemsByKey.get(key);
870
+ if (group === undefined) {
871
+ itemsByKey.set(key, [item]);
872
+ } else {
873
+ group.push(item);
874
+ }
875
+ }
876
+ return itemsByKey;
877
+ }
878
+
847
879
  /**
848
880
  * Represents a group of elements that have a common key.
849
881
  * @template TKey, T
File without changes
package/src/draft.ts CHANGED
@@ -39,7 +39,7 @@ type DraftMethods<T> = {
39
39
  *
40
40
  * @returns Array of modified property keys (does not recurse into children).
41
41
  */
42
- modifiedProps(): (keyof T)[];
42
+ getModifiedProps(): (keyof T)[];
43
43
 
44
44
  /**
45
45
  * Manually mark (or unmark) a property as modified.
@@ -155,7 +155,7 @@ function deepDraft(value: any, parentLink?: ParentLink): any {
155
155
  /**
156
156
  * Create a draft proxy over `original`. The proxy exposes the same properties
157
157
  * as the original (with nested objects wrapped as child drafts) plus the
158
- * control methods `commit`, `rollback`, `isModified`, `modifiedProps` and
158
+ * control methods `commit`, `rollback`, `isModified`, `getModifiedProps` and
159
159
  * `setModified`.
160
160
  *
161
161
  * Mutations on the draft do not affect the original until `commit()` is called.
@@ -249,7 +249,7 @@ export default function draft<T extends object>(
249
249
  return key === undefined ? touched.size > 0 : touched.has(key);
250
250
  },
251
251
 
252
- modifiedProps() {
252
+ getModifiedProps() {
253
253
  return [...touched] as (keyof T)[];
254
254
  },
255
255
 
package/src/event-bus.ts CHANGED
File without changes
package/src/functions.ts CHANGED
@@ -17,7 +17,7 @@ export interface ThrottleOptions {
17
17
  * const refresh = throttle(loadData, 250);
18
18
  * refresh();
19
19
  *
20
- * if (refresh.pending()) {
20
+ * if (refresh.isPending()) {
21
21
  * refresh.flush();
22
22
  * }
23
23
  * ```
@@ -31,7 +31,7 @@ export type DeferredFunction<F extends AnyFunction> = ((
31
31
  /** Execute the scheduled trailing call immediately, when one exists. */
32
32
  flush(): ReturnType<F> | undefined;
33
33
  /** Return true while a trailing call is scheduled. */
34
- pending(): boolean;
34
+ isPending(): boolean;
35
35
  };
36
36
 
37
37
  /**
@@ -300,7 +300,7 @@ export function throttle<F extends AnyFunction>(
300
300
  return result;
301
301
  };
302
302
 
303
- throttled.pending = (): boolean => timer !== undefined;
303
+ throttled.isPending = (): boolean => timer !== undefined;
304
304
 
305
305
  return throttled;
306
306
  }
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  export * from './types.js';
5
5
  export {default as Type} from './type.js';
6
6
  export {default as TypeCode} from './type-code.js';
7
+ export {default as Uuid} from './uuid.js';
7
8
 
8
9
  // ─── Utilities ───────────────────────────────────────
9
10
  export * from './utilities.js';
@@ -41,7 +42,7 @@ export {default as draft} from './draft.js';
41
42
  // ─── Web ─────────────────────────────────────────────
42
43
  export {default as CleanURLSearchParams} from './clean-url-search-params.js';
43
44
  /** @deprecated */
44
- export {ajaxSubmit, ajaxSubmission, submitForm, formToObject} from './browser/index.js';
45
+ export {ajaxSubmit, submitForm, formToObject} from './browser/index.js';
45
46
 
46
47
 
47
48
 
File without changes
package/src/lazy.ts CHANGED
File without changes
package/src/math.ts CHANGED
@@ -1,15 +1,27 @@
1
1
  /**
2
- * Linearly interpolates a number between two ranges.
2
+ * Linearly interpolates between two numbers: `a` for `t` 0, `b` for `t` 1, and beyond them outside that range.
3
3
  *
4
- * @param {number} n - The number to interpolate.
4
+ * @param {number} a - The value at `t` 0.
5
+ * @param {number} b - The value at `t` 1.
6
+ * @param {number} t - The interpolation factor.
7
+ * @return {number} - The interpolated value.
8
+ */
9
+ export function lerp(a: number, b: number, t: number): number {
10
+ return a + (b - a) * t;
11
+ }
12
+
13
+ /**
14
+ * Maps a number from one range to another, keeping its proportion (`remap(5, 0, 10, 0, 100)` is 50).
15
+ *
16
+ * @param {number} n - The number to map.
5
17
  * @param {number} inMin - The minimum value of the input range.
6
18
  * @param {number} inMax - The maximum value of the input range.
7
19
  * @param {number} outMin - The minimum value of the output range.
8
20
  * @param {number} outMax - The maximum value of the output range.
9
- *
10
- * @return {number} - The interpolated value.
21
+ * @return {number} - The number in the output range.
22
+ * @throws {Error} When the input range is empty (`inMin === inMax`).
11
23
  */
12
- export function lerp(n: number, inMin: number, inMax: number, outMin: number, outMax: number): number {
24
+ export function remap(n: number, inMin: number, inMax: number, outMin: number, outMax: number): number {
13
25
  if (inMax === inMin) {
14
26
  throw new Error("Input range cannot be zero");
15
27
  }
package/src/order.ts CHANGED
File without changes
package/src/parse.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // parse.ts
2
2
  /**
3
- * Options for {@link parseBoolean}.
3
+ * Options for {@link tryParseBoolean} and {@link parseBoolean}.
4
4
  */
5
5
  export interface ParseBooleanOptions {
6
6
  /**
@@ -19,7 +19,7 @@ export interface ParseBooleanOptions {
19
19
  }
20
20
 
21
21
  /**
22
- * Options for {@link parseInteger}.
22
+ * Options for {@link tryParseInteger} and {@link parseInteger}.
23
23
  */
24
24
  export interface ParseIntegerOptions {
25
25
  /**
@@ -37,7 +37,7 @@ export interface ParseIntegerOptions {
37
37
  }
38
38
 
39
39
  /**
40
- * Options for {@link parseDecimal}.
40
+ * Options for {@link tryParseDecimal} and {@link parseDecimal}.
41
41
  */
42
42
  export interface ParseDecimalOptions {
43
43
  /**
@@ -75,64 +75,78 @@ function isValidInteger(value: string, radix: number): boolean {
75
75
  }
76
76
 
77
77
  // ============================================
78
- // parseInteger
78
+ // tryParseInteger / parseInteger
79
79
  // ============================================
80
80
 
81
81
  /**
82
82
  * Safely parses a value into an integer.
83
- * Returns `null` if the value cannot be parsed.
83
+ * Returns `undefined` if the value cannot be parsed.
84
84
  *
85
- * - `null` or `undefined` → `null`
85
+ * - `null` or `undefined` → `undefined`
86
86
  * - `number` → returned as-is (floats are truncated)
87
87
  * - `string` → parsed with the given options
88
88
  *
89
89
  * @param value - The value to parse.
90
90
  * @param options - Optional parsing configuration.
91
- * @returns The parsed integer, or `null` on failure.
91
+ * @returns The parsed integer, or `undefined` on failure.
92
92
  *
93
93
  * @example
94
94
  * ```ts
95
- * parseInteger(42) // 42
96
- * parseInteger(3.7) // 3
97
- * parseInteger(null) // null
98
- * parseInteger(undefined) // null
99
- * parseInteger("42") // 42
100
- * parseInteger("1.234.567", { thousandsSeparator: "." }) // 1234567
101
- * parseInteger("FF", { radix: 16 }) // 255
102
- * parseInteger("abc") // null
95
+ * tryParseInteger(42) // 42
96
+ * tryParseInteger(3.7) // 3
97
+ * tryParseInteger(null) // undefined
98
+ * tryParseInteger(undefined) // undefined
99
+ * tryParseInteger("42") // 42
100
+ * tryParseInteger("1.234.567", { thousandsSeparator: "." }) // 1234567
101
+ * tryParseInteger("FF", { radix: 16 }) // 255
102
+ * tryParseInteger("abc") // undefined
103
103
  * ```
104
104
  */
105
- export function parseInteger(
105
+ export function tryParseInteger(
106
106
  value: string | number | null | undefined,
107
107
  options: ParseIntegerOptions = {}
108
- ): number | null {
109
- if (value === null || value === undefined) return null;
108
+ ): number | undefined {
109
+ if (value === null || value === undefined) return undefined;
110
110
 
111
111
  if (typeof value === "number") {
112
- return Number.isFinite(value) ? Math.trunc(value) : null;
112
+ return Number.isFinite(value) ? Math.trunc(value) : undefined;
113
113
  }
114
114
 
115
115
  const { radix = 10, thousandsSeparator } = options;
116
116
 
117
117
  const cleaned = stripThousands(value.trim(), thousandsSeparator);
118
118
 
119
- if (cleaned === "") return null;
120
- if (!isValidInteger(cleaned, radix)) return null;
119
+ if (cleaned === "") return undefined;
120
+ if (!isValidInteger(cleaned, radix)) return undefined;
121
121
 
122
122
  const parsed = Number.parseInt(cleaned, radix);
123
123
 
124
- return Number.isNaN(parsed) ? null : parsed;
124
+ return Number.isNaN(parsed) ? undefined : parsed;
125
+ }
126
+
127
+ /**
128
+ * Parses a value into an integer as {@link tryParseInteger} does.
129
+ *
130
+ * @throws {SyntaxError} If the value cannot be parsed.
131
+ */
132
+ export function parseInteger(value: string | number, options: ParseIntegerOptions = {}): number {
133
+ const parsed = tryParseInteger(value, options);
134
+ if (parsed === undefined) {
135
+ throw new SyntaxError(`Invalid integer "${value}".`);
136
+ }
137
+
138
+ return parsed;
125
139
  }
126
140
 
127
141
  // ============================================
128
- // parseDecimal
142
+ // tryParseDecimal / parseDecimal
129
143
  // ============================================
130
144
 
131
145
  /**
132
146
  * Safely parses a value into a floating-point number.
133
- * Returns `null` if the value cannot be parsed.
147
+ * Returns `undefined` if the value cannot be parsed.
134
148
  *
135
- * - `null` or `undefined` → `null`
149
+ * - `null` or `undefined` → `undefined`
136
150
  * - `number` → returned as-is (if finite)
137
151
  * - `string` → parsed with the given options
138
152
  *
@@ -141,50 +155,64 @@ export function parseInteger(
141
155
  *
142
156
  * @param value - The value to parse.
143
157
  * @param options - Optional parsing configuration.
144
- * @returns The parsed float, or `null` on failure.
158
+ * @returns The parsed float, or `undefined` on failure.
145
159
  *
146
160
  * @example
147
161
  * ```ts
148
- * parseDecimal(3.14) // 3.14
149
- * parseDecimal(null) // null
150
- * parseDecimal(undefined) // null
151
- * parseDecimal("3.14") // 3.14
152
- * parseDecimal("3,14", { decimalSeparator: "," }) // 3.14
153
- * parseDecimal("1.234,56", { decimalSeparator: ",", thousandsSeparator: "." }) // 1234.56
154
- * parseDecimal("abc") // null
162
+ * tryParseDecimal(3.14) // 3.14
163
+ * tryParseDecimal(null) // undefined
164
+ * tryParseDecimal(undefined) // undefined
165
+ * tryParseDecimal("3.14") // 3.14
166
+ * tryParseDecimal("3,14", { decimalSeparator: "," }) // 3.14
167
+ * tryParseDecimal("1.234,56", { decimalSeparator: ",", thousandsSeparator: "." }) // 1234.56
168
+ * tryParseDecimal("abc") // undefined
155
169
  * ```
156
170
  */
157
- export function parseDecimal(
171
+ export function tryParseDecimal(
158
172
  value: string | number | null | undefined,
159
173
  options: ParseDecimalOptions = {}
160
- ): number | null {
161
- if (value === null || value === undefined) return null;
174
+ ): number | undefined {
175
+ if (value === null || value === undefined) return undefined;
162
176
 
163
177
  if (typeof value === "number") {
164
- return Number.isFinite(value) ? value : null;
178
+ return Number.isFinite(value) ? value : undefined;
165
179
  }
166
180
 
167
181
  const { decimalSeparator = ".", thousandsSeparator } = options;
168
182
 
169
183
  let cleaned = stripThousands(value.trim(), thousandsSeparator);
170
184
 
171
- if (cleaned === "") return null;
185
+ if (cleaned === "") return undefined;
172
186
 
173
187
  if (decimalSeparator === ",") {
174
188
  const commaCount = (cleaned.match(/,/g) || []).length;
175
- if (commaCount > 1) return null;
189
+ if (commaCount > 1) return undefined;
176
190
  cleaned = cleaned.replace(",", ".");
177
191
  }
178
192
 
179
- if (!/^[+-]?\d+(\.\d+)?$/.test(cleaned)) return null;
193
+ if (!/^[+-]?\d+(\.\d+)?$/.test(cleaned)) return undefined;
180
194
 
181
195
  const parsed = Number.parseFloat(cleaned);
182
196
 
183
- return Number.isNaN(parsed) || !Number.isFinite(parsed) ? null : parsed;
197
+ return Number.isNaN(parsed) || !Number.isFinite(parsed) ? undefined : parsed;
198
+ }
199
+
200
+ /**
201
+ * Parses a value into a decimal number as {@link tryParseDecimal} does.
202
+ *
203
+ * @throws {SyntaxError} If the value cannot be parsed.
204
+ */
205
+ export function parseDecimal(value: string | number, options: ParseDecimalOptions = {}): number {
206
+ const parsed = tryParseDecimal(value, options);
207
+ if (parsed === undefined) {
208
+ throw new SyntaxError(`Invalid decimal number "${value}".`);
209
+ }
210
+
211
+ return parsed;
184
212
  }
185
213
 
186
214
  // ============================================
187
- // parseBoolean
215
+ // tryParseBoolean / parseBoolean
188
216
  // ============================================
189
217
 
190
218
  const DEFAULT_TRUTHY = ["true", "1", "yes", "si", "sí", "on"];
@@ -192,9 +220,9 @@ const DEFAULT_FALSY = ["false", "0", "no", "off"];
192
220
 
193
221
  /**
194
222
  * Safely parses a value into a boolean.
195
- * Returns `null` if the value cannot be interpreted as boolean.
223
+ * Returns `undefined` if the value cannot be interpreted as boolean.
196
224
  *
197
- * - `null` or `undefined` → `null`
225
+ * - `null` or `undefined` → `undefined`
198
226
  * - `boolean` → returned as-is
199
227
  * - `number` → `0` is `false`, any other finite number is `true`
200
228
  * - `string` → matched case-insensitively against known truthy/falsy values
@@ -204,43 +232,43 @@ const DEFAULT_FALSY = ["false", "0", "no", "off"];
204
232
  *
205
233
  * @param value - The value to parse.
206
234
  * @param options - Optional parsing configuration.
207
- * @returns The parsed boolean, or `null` on failure.
235
+ * @returns The parsed boolean, or `undefined` on failure.
208
236
  *
209
237
  * @example
210
238
  * ```ts
211
- * parseBoolean(true) // true
212
- * parseBoolean(false) // false
213
- * parseBoolean(1) // true
214
- * parseBoolean(0) // false
215
- * parseBoolean(null) // null
216
- * parseBoolean(undefined) // null
217
- * parseBoolean("yes") // true
218
- * parseBoolean("no") // false
219
- * parseBoolean("sí") // true
220
- * parseBoolean("TRUE") // true
221
- * parseBoolean("oui") // null
222
- * parseBoolean("oui", { extraTruthy: ["oui"] }) // true
223
- * parseBoolean("nein", { extraFalsy: ["nein"] }) // false
224
- * parseBoolean("whatever") // null
239
+ * tryParseBoolean(true) // true
240
+ * tryParseBoolean(false) // false
241
+ * tryParseBoolean(1) // true
242
+ * tryParseBoolean(0) // false
243
+ * tryParseBoolean(null) // undefined
244
+ * tryParseBoolean(undefined) // undefined
245
+ * tryParseBoolean("yes") // true
246
+ * tryParseBoolean("no") // false
247
+ * tryParseBoolean("sí") // true
248
+ * tryParseBoolean("TRUE") // true
249
+ * tryParseBoolean("oui") // undefined
250
+ * tryParseBoolean("oui", { extraTruthy: ["oui"] }) // true
251
+ * tryParseBoolean("nein", { extraFalsy: ["nein"] }) // false
252
+ * tryParseBoolean("whatever") // undefined
225
253
  * ```
226
254
  */
227
- export function parseBoolean(
255
+ export function tryParseBoolean(
228
256
  value: string | number | boolean | null | undefined,
229
257
  options: ParseBooleanOptions = {}
230
- ): boolean | null {
231
- if (value === null || value === undefined) return null;
258
+ ): boolean | undefined {
259
+ if (value === null || value === undefined) return undefined;
232
260
 
233
261
  if (typeof value === "boolean") return value;
234
262
 
235
263
  if (typeof value === "number") {
236
- return Number.isFinite(value) ? value !== 0 : null;
264
+ return Number.isFinite(value) ? value !== 0 : undefined;
237
265
  }
238
266
 
239
267
  const { extraTruthy = [], extraFalsy = [] } = options;
240
268
 
241
269
  const normalized = value.trim().toLowerCase();
242
270
 
243
- if (normalized === "") return null;
271
+ if (normalized === "") return undefined;
244
272
 
245
273
  const truthy = [...DEFAULT_TRUTHY, ...extraTruthy.map((s) => s.toLowerCase())];
246
274
  const falsy = [...DEFAULT_FALSY, ...extraFalsy.map((s) => s.toLowerCase())];
@@ -248,5 +276,19 @@ export function parseBoolean(
248
276
  if (truthy.includes(normalized)) return true;
249
277
  if (falsy.includes(normalized)) return false;
250
278
 
251
- return null;
252
- }
279
+ return undefined;
280
+ }
281
+
282
+ /**
283
+ * Parses a value into a boolean as {@link tryParseBoolean} does.
284
+ *
285
+ * @throws {SyntaxError} If the value cannot be parsed.
286
+ */
287
+ export function parseBoolean(value: string | number | boolean, options: ParseBooleanOptions = {}): boolean {
288
+ const parsed = tryParseBoolean(value, options);
289
+ if (parsed === undefined) {
290
+ throw new SyntaxError(`Invalid boolean "${value}".`);
291
+ }
292
+
293
+ return parsed;
294
+ }
package/src/patch.ts CHANGED
File without changes