@develia/commons 0.4.12 → 0.4.14

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 (256) hide show
  1. package/dist/cjs/array.js +4 -5
  2. package/dist/cjs/array.js.map +1 -1
  3. package/dist/cjs/async-pool-executor.js +0 -0
  4. package/dist/cjs/async-pool-executor.js.map +0 -0
  5. package/dist/cjs/browser/form-utilities.js +12 -12
  6. package/dist/cjs/browser/form-utilities.js.map +1 -1
  7. package/dist/cjs/browser/geo-location.js +1 -1
  8. package/dist/cjs/browser/geo-location.js.map +1 -1
  9. package/dist/cjs/browser/index.js +30 -2
  10. package/dist/cjs/browser/index.js.map +1 -1
  11. package/dist/cjs/browser/native-bridge.js +0 -0
  12. package/dist/cjs/browser/native-bridge.js.map +0 -0
  13. package/dist/cjs/cancellable-promise.js +0 -0
  14. package/dist/cjs/cancellable-promise.js.map +0 -0
  15. package/dist/cjs/clean-url-search-params.js +0 -0
  16. package/dist/cjs/clean-url-search-params.js.map +0 -0
  17. package/dist/cjs/collections/cache-dictionary.js +0 -0
  18. package/dist/cjs/collections/cache-dictionary.js.map +0 -0
  19. package/dist/cjs/collections/default-map.js +0 -0
  20. package/dist/cjs/collections/default-map.js.map +0 -0
  21. package/dist/cjs/collections/default-weak-map.js +0 -0
  22. package/dist/cjs/collections/default-weak-map.js.map +0 -0
  23. package/dist/cjs/collections/index.js +0 -0
  24. package/dist/cjs/collections/index.js.map +0 -0
  25. package/dist/cjs/draft.js +153 -0
  26. package/dist/cjs/draft.js.map +1 -0
  27. package/dist/cjs/event-bus.js +0 -0
  28. package/dist/cjs/event-bus.js.map +0 -0
  29. package/dist/cjs/from.js +1 -1
  30. package/dist/cjs/from.js.map +1 -1
  31. package/dist/cjs/index.js +3 -1
  32. package/dist/cjs/index.js.map +1 -1
  33. package/dist/cjs/key-value-pair.js +0 -0
  34. package/dist/cjs/key-value-pair.js.map +0 -0
  35. package/dist/cjs/lazy.js +0 -0
  36. package/dist/cjs/lazy.js.map +0 -0
  37. package/dist/cjs/math.js +2 -3
  38. package/dist/cjs/math.js.map +1 -1
  39. package/dist/cjs/order.js +0 -0
  40. package/dist/cjs/order.js.map +0 -0
  41. package/dist/cjs/parse.js +3 -4
  42. package/dist/cjs/parse.js.map +1 -1
  43. package/dist/cjs/patch.js +2 -3
  44. package/dist/cjs/patch.js.map +1 -1
  45. package/dist/cjs/stopwatch.js +0 -0
  46. package/dist/cjs/stopwatch.js.map +0 -0
  47. package/dist/cjs/string.js +8 -9
  48. package/dist/cjs/string.js.map +1 -1
  49. package/dist/cjs/timer.js +0 -0
  50. package/dist/cjs/timer.js.map +0 -0
  51. package/dist/cjs/timespan.js +0 -0
  52. package/dist/cjs/timespan.js.map +0 -0
  53. package/dist/cjs/type-code.js +0 -0
  54. package/dist/cjs/type-code.js.map +0 -0
  55. package/dist/cjs/type.js +0 -0
  56. package/dist/cjs/type.js.map +0 -0
  57. package/dist/cjs/types.js +0 -0
  58. package/dist/cjs/types.js.map +0 -0
  59. package/dist/cjs/utilities.js +106 -57
  60. package/dist/cjs/utilities.js.map +1 -1
  61. package/dist/esm/array.js +0 -0
  62. package/dist/esm/array.js.map +0 -0
  63. package/dist/esm/async-pool-executor.js +0 -0
  64. package/dist/esm/async-pool-executor.js.map +0 -0
  65. package/dist/esm/browser/form-utilities.js +7 -6
  66. package/dist/esm/browser/form-utilities.js.map +1 -1
  67. package/dist/esm/browser/geo-location.js +0 -0
  68. package/dist/esm/browser/geo-location.js.map +0 -0
  69. package/dist/esm/browser/index.js +27 -0
  70. package/dist/esm/browser/index.js.map +1 -1
  71. package/dist/esm/browser/native-bridge.js +0 -0
  72. package/dist/esm/browser/native-bridge.js.map +0 -0
  73. package/dist/esm/cancellable-promise.js +0 -0
  74. package/dist/esm/cancellable-promise.js.map +0 -0
  75. package/dist/esm/clean-url-search-params.js +0 -0
  76. package/dist/esm/clean-url-search-params.js.map +0 -0
  77. package/dist/esm/collections/cache-dictionary.js +0 -0
  78. package/dist/esm/collections/cache-dictionary.js.map +0 -0
  79. package/dist/esm/collections/default-map.js +0 -0
  80. package/dist/esm/collections/default-map.js.map +0 -0
  81. package/dist/esm/collections/default-weak-map.js +0 -0
  82. package/dist/esm/collections/default-weak-map.js.map +0 -0
  83. package/dist/esm/collections/index.js +0 -0
  84. package/dist/esm/collections/index.js.map +0 -0
  85. package/dist/esm/draft.js +150 -0
  86. package/dist/esm/draft.js.map +1 -0
  87. package/dist/esm/event-bus.js +0 -0
  88. package/dist/esm/event-bus.js.map +0 -0
  89. package/dist/esm/from.js +0 -0
  90. package/dist/esm/from.js.map +0 -0
  91. package/dist/esm/index.js +1 -0
  92. package/dist/esm/index.js.map +1 -1
  93. package/dist/esm/key-value-pair.js +0 -0
  94. package/dist/esm/key-value-pair.js.map +0 -0
  95. package/dist/esm/lazy.js +0 -0
  96. package/dist/esm/lazy.js.map +0 -0
  97. package/dist/esm/math.js +0 -0
  98. package/dist/esm/math.js.map +0 -0
  99. package/dist/esm/order.js +0 -0
  100. package/dist/esm/order.js.map +0 -0
  101. package/dist/esm/parse.js +0 -0
  102. package/dist/esm/parse.js.map +0 -0
  103. package/dist/esm/patch.js +0 -0
  104. package/dist/esm/patch.js.map +0 -0
  105. package/dist/esm/stopwatch.js +0 -0
  106. package/dist/esm/stopwatch.js.map +0 -0
  107. package/dist/esm/string.js +0 -0
  108. package/dist/esm/string.js.map +0 -0
  109. package/dist/esm/timer.js +0 -0
  110. package/dist/esm/timer.js.map +0 -0
  111. package/dist/esm/timespan.js +0 -0
  112. package/dist/esm/timespan.js.map +0 -0
  113. package/dist/esm/type-code.js +0 -0
  114. package/dist/esm/type-code.js.map +0 -0
  115. package/dist/esm/type.js +0 -0
  116. package/dist/esm/type.js.map +0 -0
  117. package/dist/esm/types.js +0 -0
  118. package/dist/esm/types.js.map +0 -0
  119. package/dist/esm/utilities.js +81 -32
  120. package/dist/esm/utilities.js.map +1 -1
  121. package/dist/types/array.d.ts +0 -0
  122. package/dist/types/array.d.ts.map +0 -0
  123. package/dist/types/async-pool-executor.d.ts +0 -0
  124. package/dist/types/async-pool-executor.d.ts.map +0 -0
  125. package/dist/types/browser/form-utilities.d.ts +0 -0
  126. package/dist/types/browser/form-utilities.d.ts.map +1 -1
  127. package/dist/types/browser/geo-location.d.ts +0 -0
  128. package/dist/types/browser/geo-location.d.ts.map +0 -0
  129. package/dist/types/browser/index.d.ts +3 -0
  130. package/dist/types/browser/index.d.ts.map +1 -1
  131. package/dist/types/browser/native-bridge.d.ts +0 -0
  132. package/dist/types/browser/native-bridge.d.ts.map +0 -0
  133. package/dist/types/cancellable-promise.d.ts +0 -0
  134. package/dist/types/cancellable-promise.d.ts.map +0 -0
  135. package/dist/types/clean-url-search-params.d.ts +0 -0
  136. package/dist/types/clean-url-search-params.d.ts.map +0 -0
  137. package/dist/types/collections/cache-dictionary.d.ts +0 -0
  138. package/dist/types/collections/cache-dictionary.d.ts.map +0 -0
  139. package/dist/types/collections/default-map.d.ts +0 -0
  140. package/dist/types/collections/default-map.d.ts.map +0 -0
  141. package/dist/types/collections/default-weak-map.d.ts +0 -0
  142. package/dist/types/collections/default-weak-map.d.ts.map +0 -0
  143. package/dist/types/collections/index.d.ts +0 -0
  144. package/dist/types/collections/index.d.ts.map +0 -0
  145. package/dist/types/draft.d.ts +17 -0
  146. package/dist/types/draft.d.ts.map +1 -0
  147. package/dist/types/event-bus.d.ts +0 -0
  148. package/dist/types/event-bus.d.ts.map +0 -0
  149. package/dist/types/from.d.ts +0 -0
  150. package/dist/types/from.d.ts.map +0 -0
  151. package/dist/types/index.d.ts +1 -0
  152. package/dist/types/index.d.ts.map +1 -1
  153. package/dist/types/key-value-pair.d.ts +0 -0
  154. package/dist/types/key-value-pair.d.ts.map +0 -0
  155. package/dist/types/lazy.d.ts +0 -0
  156. package/dist/types/lazy.d.ts.map +0 -0
  157. package/dist/types/math.d.ts +0 -0
  158. package/dist/types/math.d.ts.map +0 -0
  159. package/dist/types/order.d.ts +0 -0
  160. package/dist/types/order.d.ts.map +0 -0
  161. package/dist/types/parse.d.ts +0 -0
  162. package/dist/types/parse.d.ts.map +0 -0
  163. package/dist/types/patch.d.ts +0 -0
  164. package/dist/types/patch.d.ts.map +0 -0
  165. package/dist/types/stopwatch.d.ts +0 -0
  166. package/dist/types/stopwatch.d.ts.map +0 -0
  167. package/dist/types/string.d.ts +0 -0
  168. package/dist/types/string.d.ts.map +0 -0
  169. package/dist/types/timer.d.ts +0 -0
  170. package/dist/types/timer.d.ts.map +0 -0
  171. package/dist/types/timespan.d.ts +0 -0
  172. package/dist/types/timespan.d.ts.map +0 -0
  173. package/dist/types/type-code.d.ts +0 -0
  174. package/dist/types/type-code.d.ts.map +0 -0
  175. package/dist/types/type.d.ts +0 -0
  176. package/dist/types/type.d.ts.map +0 -0
  177. package/dist/types/types.d.ts +0 -0
  178. package/dist/types/types.d.ts.map +0 -0
  179. package/dist/types/utilities.d.ts +3 -2
  180. package/dist/types/utilities.d.ts.map +1 -1
  181. package/dist/web/array.js +0 -0
  182. package/dist/web/array.js.map +0 -0
  183. package/dist/web/async-pool-executor.js +0 -0
  184. package/dist/web/async-pool-executor.js.map +0 -0
  185. package/dist/web/browser/form-utilities.js +7 -6
  186. package/dist/web/browser/form-utilities.js.map +1 -1
  187. package/dist/web/browser/geo-location.js +0 -0
  188. package/dist/web/browser/geo-location.js.map +0 -0
  189. package/dist/web/browser/index.js +27 -0
  190. package/dist/web/browser/index.js.map +1 -1
  191. package/dist/web/browser/native-bridge.js +0 -0
  192. package/dist/web/browser/native-bridge.js.map +0 -0
  193. package/dist/web/cancellable-promise.js +0 -0
  194. package/dist/web/cancellable-promise.js.map +0 -0
  195. package/dist/web/clean-url-search-params.js +0 -0
  196. package/dist/web/clean-url-search-params.js.map +0 -0
  197. package/dist/web/collections/cache-dictionary.js +0 -0
  198. package/dist/web/collections/cache-dictionary.js.map +0 -0
  199. package/dist/web/collections/default-map.js +0 -0
  200. package/dist/web/collections/default-map.js.map +0 -0
  201. package/dist/web/collections/default-weak-map.js +0 -0
  202. package/dist/web/collections/default-weak-map.js.map +0 -0
  203. package/dist/web/collections/index.js +0 -0
  204. package/dist/web/collections/index.js.map +0 -0
  205. package/dist/web/draft.js +150 -0
  206. package/dist/web/draft.js.map +1 -0
  207. package/dist/web/event-bus.js +0 -0
  208. package/dist/web/event-bus.js.map +0 -0
  209. package/dist/web/from.js +0 -0
  210. package/dist/web/from.js.map +0 -0
  211. package/dist/web/index.js +1 -0
  212. package/dist/web/index.js.map +1 -1
  213. package/dist/web/key-value-pair.js +0 -0
  214. package/dist/web/key-value-pair.js.map +0 -0
  215. package/dist/web/lazy.js +0 -0
  216. package/dist/web/lazy.js.map +0 -0
  217. package/dist/web/math.js +0 -0
  218. package/dist/web/math.js.map +0 -0
  219. package/dist/web/order.js +0 -0
  220. package/dist/web/order.js.map +0 -0
  221. package/dist/web/parse.js +0 -0
  222. package/dist/web/parse.js.map +0 -0
  223. package/dist/web/patch.js +0 -0
  224. package/dist/web/patch.js.map +0 -0
  225. package/dist/web/stopwatch.js +0 -0
  226. package/dist/web/stopwatch.js.map +0 -0
  227. package/dist/web/string.js +0 -0
  228. package/dist/web/string.js.map +0 -0
  229. package/dist/web/timer.js +0 -0
  230. package/dist/web/timer.js.map +0 -0
  231. package/dist/web/timespan.js +0 -0
  232. package/dist/web/timespan.js.map +0 -0
  233. package/dist/web/type-code.js +0 -0
  234. package/dist/web/type-code.js.map +0 -0
  235. package/dist/web/type.js +0 -0
  236. package/dist/web/type.js.map +0 -0
  237. package/dist/web/types.js +0 -0
  238. package/dist/web/types.js.map +0 -0
  239. package/dist/web/utilities.js +81 -32
  240. package/dist/web/utilities.js.map +1 -1
  241. package/package.json +60 -56
  242. package/src/browser/form-utilities.ts +7 -6
  243. package/src/browser/geo-location.ts +113 -113
  244. package/src/browser/index.ts +37 -0
  245. package/src/browser/native-bridge.ts +116 -116
  246. package/src/cancellable-promise.ts +76 -76
  247. package/src/collections/index.ts +2 -2
  248. package/src/draft.ts +268 -0
  249. package/src/event-bus.ts +83 -83
  250. package/src/index.ts +42 -37
  251. package/src/parse.ts +240 -240
  252. package/src/string.ts +128 -128
  253. package/src/type-code.ts +15 -15
  254. package/src/type.ts +371 -371
  255. package/src/types.ts +30 -30
  256. package/src/utilities.ts +480 -404
package/src/parse.ts CHANGED
@@ -1,241 +1,241 @@
1
- // parse.ts
2
- /**
3
- * Options for {@link parseBoolean}.
4
- */
5
- export interface ParseBooleanOptions {
6
- /**
7
- * Additional strings to consider as `true`.
8
- * These are matched case-insensitively.
9
- * @example ["oui", "ja"]
10
- */
11
- extraTruthy?: string[];
12
-
13
- /**
14
- * Additional strings to consider as `false`.
15
- * These are matched case-insensitively.
16
- * @example ["non", "nein"]
17
- */
18
- extraFalsy?: string[];
19
- }
20
-
21
- /**
22
- * Options for {@link parseInteger}.
23
- */
24
- export interface ParseIntegerOptions {
25
- /**
26
- * Numeric base for conversion.
27
- * @default 10
28
- */
29
- radix?: number;
30
-
31
- /**
32
- * Thousands separator to strip before parsing.
33
- * @example "." for "1.234.567" → 1234567
34
- * @example "," for "1,234,567" → 1234567
35
- */
36
- thousandsSeparator?: string;
37
- }
38
-
39
- /**
40
- * Options for {@link parseDecimal}.
41
- */
42
- export interface ParseDecimalOptions {
43
- /**
44
- * Character used as decimal point.
45
- * @default "."
46
- */
47
- decimalSeparator?: "." | ",";
48
-
49
- /**
50
- * Thousands separator to strip before parsing.
51
- * @example "." for "1.234,56" (EU format)
52
- * @example "," for "1,234.56" (US format)
53
- */
54
- thousandsSeparator?: string;
55
- }
56
-
57
- // ============================================
58
- // HELPERS
59
- // ============================================
60
-
61
- function stripThousands(value: string, separator?: string): string {
62
- if (!separator) return value;
63
- return value.split(separator).join("");
64
- }
65
-
66
- // ============================================
67
- // parseInteger
68
- // ============================================
69
-
70
- /**
71
- * Safely parses a value into an integer.
72
- * Returns `null` if the value cannot be parsed.
73
- *
74
- * - `null` or `undefined` → `null`
75
- * - `number` → returned as-is (floats are truncated)
76
- * - `string` → parsed with the given options
77
- *
78
- * @param value - The value to parse.
79
- * @param options - Optional parsing configuration.
80
- * @returns The parsed integer, or `null` on failure.
81
- *
82
- * @example
83
- * ```ts
84
- * parseInteger(42) // 42
85
- * parseInteger(3.7) // 3
86
- * parseInteger(null) // null
87
- * parseInteger(undefined) // null
88
- * parseInteger("42") // 42
89
- * parseInteger("1.234.567", { thousandsSeparator: "." }) // 1234567
90
- * parseInteger("FF", { radix: 16 }) // 255
91
- * parseInteger("abc") // null
92
- * ```
93
- */
94
- export function parseInteger(
95
- value: string | number | null | undefined,
96
- options: ParseIntegerOptions = {}
97
- ): number | null {
98
- if (value === null || value === undefined) return null;
99
-
100
- if (typeof value === "number") {
101
- return Number.isFinite(value) ? Math.trunc(value) : null;
102
- }
103
-
104
- const { radix = 10, thousandsSeparator } = options;
105
-
106
- const cleaned = stripThousands(value.trim(), thousandsSeparator);
107
-
108
- if (cleaned === "") return null;
109
- if (radix === 10 && !/^[+-]?\d+$/.test(cleaned)) return null;
110
-
111
- const parsed = Number.parseInt(cleaned, radix);
112
-
113
- return Number.isNaN(parsed) ? null : parsed;
114
- }
115
-
116
- // ============================================
117
- // parseDecimal
118
- // ============================================
119
-
120
- /**
121
- * Safely parses a value into a floating-point number.
122
- * Returns `null` if the value cannot be parsed.
123
- *
124
- * - `null` or `undefined` → `null`
125
- * - `number` → returned as-is (if finite)
126
- * - `string` → parsed with the given options
127
- *
128
- * Supports custom decimal and thousands separators, making it suitable
129
- * for both US (`1,234.56`) and EU (`1.234,56`) number formats.
130
- *
131
- * @param value - The value to parse.
132
- * @param options - Optional parsing configuration.
133
- * @returns The parsed float, or `null` on failure.
134
- *
135
- * @example
136
- * ```ts
137
- * parseDecimal(3.14) // 3.14
138
- * parseDecimal(null) // null
139
- * parseDecimal(undefined) // null
140
- * parseDecimal("3.14") // 3.14
141
- * parseDecimal("3,14", { decimalSeparator: "," }) // 3.14
142
- * parseDecimal("1.234,56", { decimalSeparator: ",", thousandsSeparator: "." }) // 1234.56
143
- * parseDecimal("abc") // null
144
- * ```
145
- */
146
- export function parseDecimal(
147
- value: string | number | null | undefined,
148
- options: ParseDecimalOptions = {}
149
- ): number | null {
150
- if (value === null || value === undefined) return null;
151
-
152
- if (typeof value === "number") {
153
- return Number.isFinite(value) ? value : null;
154
- }
155
-
156
- const { decimalSeparator = ".", thousandsSeparator } = options;
157
-
158
- let cleaned = stripThousands(value.trim(), thousandsSeparator);
159
-
160
- if (cleaned === "") return null;
161
-
162
- if (decimalSeparator === ",") {
163
- const commaCount = (cleaned.match(/,/g) || []).length;
164
- if (commaCount > 1) return null;
165
- cleaned = cleaned.replace(",", ".");
166
- }
167
-
168
- if (!/^[+-]?\d+(\.\d+)?$/.test(cleaned)) return null;
169
-
170
- const parsed = Number.parseFloat(cleaned);
171
-
172
- return Number.isNaN(parsed) || !Number.isFinite(parsed) ? null : parsed;
173
- }
174
-
175
- // ============================================
176
- // parseBoolean
177
- // ============================================
178
-
179
- const DEFAULT_TRUTHY = ["true", "1", "yes", "si", "sí", "on"];
180
- const DEFAULT_FALSY = ["false", "0", "no", "off"];
181
-
182
- /**
183
- * Safely parses a value into a boolean.
184
- * Returns `null` if the value cannot be interpreted as boolean.
185
- *
186
- * - `null` or `undefined` → `null`
187
- * - `boolean` → returned as-is
188
- * - `number` → `0` is `false`, any other finite number is `true`
189
- * - `string` → matched case-insensitively against known truthy/falsy values
190
- *
191
- * Default truthy: `"true"`, `"1"`, `"yes"`, `"si"`, `"sí"`, `"on"`
192
- * Default falsy: `"false"`, `"0"`, `"no"`, `"off"`
193
- *
194
- * @param value - The value to parse.
195
- * @param options - Optional parsing configuration.
196
- * @returns The parsed boolean, or `null` on failure.
197
- *
198
- * @example
199
- * ```ts
200
- * parseBoolean(true) // true
201
- * parseBoolean(false) // false
202
- * parseBoolean(1) // true
203
- * parseBoolean(0) // false
204
- * parseBoolean(null) // null
205
- * parseBoolean(undefined) // null
206
- * parseBoolean("yes") // true
207
- * parseBoolean("no") // false
208
- * parseBoolean("sí") // true
209
- * parseBoolean("TRUE") // true
210
- * parseBoolean("oui") // null
211
- * parseBoolean("oui", { extraTruthy: ["oui"] }) // true
212
- * parseBoolean("nein", { extraFalsy: ["nein"] }) // false
213
- * parseBoolean("whatever") // null
214
- * ```
215
- */
216
- export function parseBoolean(
217
- value: string | number | boolean | null | undefined,
218
- options: ParseBooleanOptions = {}
219
- ): boolean | null {
220
- if (value === null || value === undefined) return null;
221
-
222
- if (typeof value === "boolean") return value;
223
-
224
- if (typeof value === "number") {
225
- return Number.isFinite(value) ? value !== 0 : null;
226
- }
227
-
228
- const { extraTruthy = [], extraFalsy = [] } = options;
229
-
230
- const normalized = value.trim().toLowerCase();
231
-
232
- if (normalized === "") return null;
233
-
234
- const truthy = [...DEFAULT_TRUTHY, ...extraTruthy.map((s) => s.toLowerCase())];
235
- const falsy = [...DEFAULT_FALSY, ...extraFalsy.map((s) => s.toLowerCase())];
236
-
237
- if (truthy.includes(normalized)) return true;
238
- if (falsy.includes(normalized)) return false;
239
-
240
- return null;
1
+ // parse.ts
2
+ /**
3
+ * Options for {@link parseBoolean}.
4
+ */
5
+ export interface ParseBooleanOptions {
6
+ /**
7
+ * Additional strings to consider as `true`.
8
+ * These are matched case-insensitively.
9
+ * @example ["oui", "ja"]
10
+ */
11
+ extraTruthy?: string[];
12
+
13
+ /**
14
+ * Additional strings to consider as `false`.
15
+ * These are matched case-insensitively.
16
+ * @example ["non", "nein"]
17
+ */
18
+ extraFalsy?: string[];
19
+ }
20
+
21
+ /**
22
+ * Options for {@link parseInteger}.
23
+ */
24
+ export interface ParseIntegerOptions {
25
+ /**
26
+ * Numeric base for conversion.
27
+ * @default 10
28
+ */
29
+ radix?: number;
30
+
31
+ /**
32
+ * Thousands separator to strip before parsing.
33
+ * @example "." for "1.234.567" → 1234567
34
+ * @example "," for "1,234,567" → 1234567
35
+ */
36
+ thousandsSeparator?: string;
37
+ }
38
+
39
+ /**
40
+ * Options for {@link parseDecimal}.
41
+ */
42
+ export interface ParseDecimalOptions {
43
+ /**
44
+ * Character used as decimal point.
45
+ * @default "."
46
+ */
47
+ decimalSeparator?: "." | ",";
48
+
49
+ /**
50
+ * Thousands separator to strip before parsing.
51
+ * @example "." for "1.234,56" (EU format)
52
+ * @example "," for "1,234.56" (US format)
53
+ */
54
+ thousandsSeparator?: string;
55
+ }
56
+
57
+ // ============================================
58
+ // HELPERS
59
+ // ============================================
60
+
61
+ function stripThousands(value: string, separator?: string): string {
62
+ if (!separator) return value;
63
+ return value.split(separator).join("");
64
+ }
65
+
66
+ // ============================================
67
+ // parseInteger
68
+ // ============================================
69
+
70
+ /**
71
+ * Safely parses a value into an integer.
72
+ * Returns `null` if the value cannot be parsed.
73
+ *
74
+ * - `null` or `undefined` → `null`
75
+ * - `number` → returned as-is (floats are truncated)
76
+ * - `string` → parsed with the given options
77
+ *
78
+ * @param value - The value to parse.
79
+ * @param options - Optional parsing configuration.
80
+ * @returns The parsed integer, or `null` on failure.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * parseInteger(42) // 42
85
+ * parseInteger(3.7) // 3
86
+ * parseInteger(null) // null
87
+ * parseInteger(undefined) // null
88
+ * parseInteger("42") // 42
89
+ * parseInteger("1.234.567", { thousandsSeparator: "." }) // 1234567
90
+ * parseInteger("FF", { radix: 16 }) // 255
91
+ * parseInteger("abc") // null
92
+ * ```
93
+ */
94
+ export function parseInteger(
95
+ value: string | number | null | undefined,
96
+ options: ParseIntegerOptions = {}
97
+ ): number | null {
98
+ if (value === null || value === undefined) return null;
99
+
100
+ if (typeof value === "number") {
101
+ return Number.isFinite(value) ? Math.trunc(value) : null;
102
+ }
103
+
104
+ const { radix = 10, thousandsSeparator } = options;
105
+
106
+ const cleaned = stripThousands(value.trim(), thousandsSeparator);
107
+
108
+ if (cleaned === "") return null;
109
+ if (radix === 10 && !/^[+-]?\d+$/.test(cleaned)) return null;
110
+
111
+ const parsed = Number.parseInt(cleaned, radix);
112
+
113
+ return Number.isNaN(parsed) ? null : parsed;
114
+ }
115
+
116
+ // ============================================
117
+ // parseDecimal
118
+ // ============================================
119
+
120
+ /**
121
+ * Safely parses a value into a floating-point number.
122
+ * Returns `null` if the value cannot be parsed.
123
+ *
124
+ * - `null` or `undefined` → `null`
125
+ * - `number` → returned as-is (if finite)
126
+ * - `string` → parsed with the given options
127
+ *
128
+ * Supports custom decimal and thousands separators, making it suitable
129
+ * for both US (`1,234.56`) and EU (`1.234,56`) number formats.
130
+ *
131
+ * @param value - The value to parse.
132
+ * @param options - Optional parsing configuration.
133
+ * @returns The parsed float, or `null` on failure.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * parseDecimal(3.14) // 3.14
138
+ * parseDecimal(null) // null
139
+ * parseDecimal(undefined) // null
140
+ * parseDecimal("3.14") // 3.14
141
+ * parseDecimal("3,14", { decimalSeparator: "," }) // 3.14
142
+ * parseDecimal("1.234,56", { decimalSeparator: ",", thousandsSeparator: "." }) // 1234.56
143
+ * parseDecimal("abc") // null
144
+ * ```
145
+ */
146
+ export function parseDecimal(
147
+ value: string | number | null | undefined,
148
+ options: ParseDecimalOptions = {}
149
+ ): number | null {
150
+ if (value === null || value === undefined) return null;
151
+
152
+ if (typeof value === "number") {
153
+ return Number.isFinite(value) ? value : null;
154
+ }
155
+
156
+ const { decimalSeparator = ".", thousandsSeparator } = options;
157
+
158
+ let cleaned = stripThousands(value.trim(), thousandsSeparator);
159
+
160
+ if (cleaned === "") return null;
161
+
162
+ if (decimalSeparator === ",") {
163
+ const commaCount = (cleaned.match(/,/g) || []).length;
164
+ if (commaCount > 1) return null;
165
+ cleaned = cleaned.replace(",", ".");
166
+ }
167
+
168
+ if (!/^[+-]?\d+(\.\d+)?$/.test(cleaned)) return null;
169
+
170
+ const parsed = Number.parseFloat(cleaned);
171
+
172
+ return Number.isNaN(parsed) || !Number.isFinite(parsed) ? null : parsed;
173
+ }
174
+
175
+ // ============================================
176
+ // parseBoolean
177
+ // ============================================
178
+
179
+ const DEFAULT_TRUTHY = ["true", "1", "yes", "si", "sí", "on"];
180
+ const DEFAULT_FALSY = ["false", "0", "no", "off"];
181
+
182
+ /**
183
+ * Safely parses a value into a boolean.
184
+ * Returns `null` if the value cannot be interpreted as boolean.
185
+ *
186
+ * - `null` or `undefined` → `null`
187
+ * - `boolean` → returned as-is
188
+ * - `number` → `0` is `false`, any other finite number is `true`
189
+ * - `string` → matched case-insensitively against known truthy/falsy values
190
+ *
191
+ * Default truthy: `"true"`, `"1"`, `"yes"`, `"si"`, `"sí"`, `"on"`
192
+ * Default falsy: `"false"`, `"0"`, `"no"`, `"off"`
193
+ *
194
+ * @param value - The value to parse.
195
+ * @param options - Optional parsing configuration.
196
+ * @returns The parsed boolean, or `null` on failure.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * parseBoolean(true) // true
201
+ * parseBoolean(false) // false
202
+ * parseBoolean(1) // true
203
+ * parseBoolean(0) // false
204
+ * parseBoolean(null) // null
205
+ * parseBoolean(undefined) // null
206
+ * parseBoolean("yes") // true
207
+ * parseBoolean("no") // false
208
+ * parseBoolean("sí") // true
209
+ * parseBoolean("TRUE") // true
210
+ * parseBoolean("oui") // null
211
+ * parseBoolean("oui", { extraTruthy: ["oui"] }) // true
212
+ * parseBoolean("nein", { extraFalsy: ["nein"] }) // false
213
+ * parseBoolean("whatever") // null
214
+ * ```
215
+ */
216
+ export function parseBoolean(
217
+ value: string | number | boolean | null | undefined,
218
+ options: ParseBooleanOptions = {}
219
+ ): boolean | null {
220
+ if (value === null || value === undefined) return null;
221
+
222
+ if (typeof value === "boolean") return value;
223
+
224
+ if (typeof value === "number") {
225
+ return Number.isFinite(value) ? value !== 0 : null;
226
+ }
227
+
228
+ const { extraTruthy = [], extraFalsy = [] } = options;
229
+
230
+ const normalized = value.trim().toLowerCase();
231
+
232
+ if (normalized === "") return null;
233
+
234
+ const truthy = [...DEFAULT_TRUTHY, ...extraTruthy.map((s) => s.toLowerCase())];
235
+ const falsy = [...DEFAULT_FALSY, ...extraFalsy.map((s) => s.toLowerCase())];
236
+
237
+ if (truthy.includes(normalized)) return true;
238
+ if (falsy.includes(normalized)) return false;
239
+
240
+ return null;
241
241
  }