@nlozgachev/pipelined 0.9.0 → 0.11.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.
Files changed (75) hide show
  1. package/README.md +9 -3
  2. package/esm/src/Core/Option.js +2 -1
  3. package/esm/src/Core/Optional.js +2 -2
  4. package/esm/src/Core/RemoteData.js +8 -8
  5. package/esm/src/Core/TaskValidation.js +35 -12
  6. package/esm/src/Core/Validation.js +3 -3
  7. package/esm/src/Core/index.js +0 -2
  8. package/esm/src/{Core → Utils}/Arr.js +95 -23
  9. package/esm/src/Utils/Num.js +124 -0
  10. package/esm/src/{Core → Utils}/Rec.js +59 -11
  11. package/esm/src/Utils/Str.js +134 -0
  12. package/esm/src/Utils/index.js +4 -0
  13. package/package.json +11 -1
  14. package/script/src/Core/Option.js +2 -1
  15. package/script/src/Core/Optional.js +2 -2
  16. package/script/src/Core/RemoteData.js +8 -8
  17. package/script/src/Core/TaskValidation.js +35 -12
  18. package/script/src/Core/Validation.js +3 -3
  19. package/script/src/Core/index.js +0 -2
  20. package/script/src/{Core → Utils}/Arr.js +95 -23
  21. package/script/src/Utils/Num.js +127 -0
  22. package/script/src/{Core → Utils}/Rec.js +59 -11
  23. package/script/src/Utils/Str.js +137 -0
  24. package/script/src/Utils/index.js +20 -0
  25. package/types/src/Composition/compose.d.ts.map +1 -1
  26. package/types/src/Composition/converge.d.ts.map +1 -1
  27. package/types/src/Composition/curry.d.ts.map +1 -1
  28. package/types/src/Composition/flow.d.ts.map +1 -1
  29. package/types/src/Composition/fn.d.ts.map +1 -1
  30. package/types/src/Composition/juxt.d.ts.map +1 -1
  31. package/types/src/Composition/memoize.d.ts.map +1 -1
  32. package/types/src/Composition/not.d.ts.map +1 -1
  33. package/types/src/Composition/on.d.ts.map +1 -1
  34. package/types/src/Composition/pipe.d.ts.map +1 -1
  35. package/types/src/Composition/uncurry.d.ts.map +1 -1
  36. package/types/src/Core/Deferred.d.ts.map +1 -1
  37. package/types/src/Core/Lens.d.ts.map +1 -1
  38. package/types/src/Core/Logged.d.ts.map +1 -1
  39. package/types/src/Core/Option.d.ts +1 -1
  40. package/types/src/Core/Option.d.ts.map +1 -1
  41. package/types/src/Core/Optional.d.ts +2 -2
  42. package/types/src/Core/Optional.d.ts.map +1 -1
  43. package/types/src/Core/Predicate.d.ts.map +1 -1
  44. package/types/src/Core/Reader.d.ts.map +1 -1
  45. package/types/src/Core/Refinement.d.ts.map +1 -1
  46. package/types/src/Core/RemoteData.d.ts +3 -3
  47. package/types/src/Core/RemoteData.d.ts.map +1 -1
  48. package/types/src/Core/Result.d.ts +1 -1
  49. package/types/src/Core/Result.d.ts.map +1 -1
  50. package/types/src/Core/State.d.ts.map +1 -1
  51. package/types/src/Core/Task.d.ts.map +1 -1
  52. package/types/src/Core/TaskOption.d.ts +1 -1
  53. package/types/src/Core/TaskOption.d.ts.map +1 -1
  54. package/types/src/Core/TaskResult.d.ts.map +1 -1
  55. package/types/src/Core/TaskValidation.d.ts +31 -8
  56. package/types/src/Core/TaskValidation.d.ts.map +1 -1
  57. package/types/src/Core/These.d.ts.map +1 -1
  58. package/types/src/Core/Validation.d.ts +3 -3
  59. package/types/src/Core/Validation.d.ts.map +1 -1
  60. package/types/src/Core/index.d.ts +0 -2
  61. package/types/src/Core/index.d.ts.map +1 -1
  62. package/types/src/Types/Brand.d.ts.map +1 -1
  63. package/types/src/Types/NonEmptyList.d.ts.map +1 -1
  64. package/types/src/{Core → Utils}/Arr.d.ts +25 -3
  65. package/types/src/Utils/Arr.d.ts.map +1 -0
  66. package/types/src/Utils/Num.d.ts +110 -0
  67. package/types/src/Utils/Num.d.ts.map +1 -0
  68. package/types/src/{Core → Utils}/Rec.d.ts +23 -1
  69. package/types/src/Utils/Rec.d.ts.map +1 -0
  70. package/types/src/Utils/Str.d.ts +128 -0
  71. package/types/src/Utils/Str.d.ts.map +1 -0
  72. package/types/src/Utils/index.d.ts +5 -0
  73. package/types/src/Utils/index.d.ts.map +1 -0
  74. package/types/src/Core/Arr.d.ts.map +0 -1
  75. package/types/src/Core/Rec.d.ts.map +0 -1
package/README.md CHANGED
@@ -17,8 +17,6 @@ state flag soup, you get types that name every possible state and make invalid o
17
17
  Each type comes with a consistent set of operations — `map`, `chain`, `match`, `getOrElse` — that
18
18
  compose with `pipe` and `flow`.
19
19
 
20
- No FP jargon required. You won't find `Monad`, `Functor`, or `Applicative` in the API.
21
-
22
20
  ## What's included?
23
21
 
24
22
  ### pipelined/core
@@ -39,8 +37,16 @@ No FP jargon required. You won't find `Monad`, `Functor`, or `Applicative` in th
39
37
  - **`Optional<S, A>`** — like `Lens`, but the target may be absent (nullable fields, array indices).
40
38
  - **`Reader<R, A>`** — a computation that depends on an environment `R`, supplied once at the
41
39
  boundary.
40
+
41
+
42
+ ### pipelined/utils
43
+
44
+ Everyday utilities for built-in JS types.
45
+
42
46
  - **`Arr`** — array utilities, data-last, returning `Option` instead of `undefined`.
43
- - **`Rec`** — record utilities, data-last, with `Option`-returning key lookup.
47
+ - **`Rec`** — record/object utilities, data-last, with `Option`-returning key lookup.
48
+ - **`Num`** — number utilities: `range`, `clamp`, `between`, safe `parse`, and curried arithmetic.
49
+ - **`Str`** — string utilities: `split`, `trim`, `words`, `lines`, and safe `parse.int` / `parse.float`.
44
50
 
45
51
  ### pipelined/types
46
52
 
@@ -1,4 +1,5 @@
1
1
  import { Result } from "./Result.js";
2
+ const _none = { kind: "None" };
2
3
  export var Option;
3
4
  (function (Option) {
4
5
  /**
@@ -12,7 +13,7 @@ export var Option;
12
13
  /**
13
14
  * Creates a None (empty Option).
14
15
  */
15
- Option.none = () => ({ kind: "None" });
16
+ Option.none = () => _none;
16
17
  /**
17
18
  * Type guard that checks if a Option is None.
18
19
  */
@@ -84,12 +84,12 @@ export var Optional;
84
84
  *
85
85
  * @example
86
86
  * ```ts
87
- * pipe(profile, Optional.getOrElse(bioOpt)("no bio"));
87
+ * pipe(profile, Optional.getOrElse(bioOpt)(() => "no bio"));
88
88
  * ```
89
89
  */
90
90
  Optional.getOrElse = (opt) => (defaultValue) => (s) => {
91
91
  const val = opt.get(s);
92
- return val.kind === "Some" ? val.value : defaultValue;
92
+ return val.kind === "Some" ? val.value : defaultValue();
93
93
  };
94
94
  /**
95
95
  * Extracts a value from an Optional focus using handlers for the present
@@ -1,15 +1,17 @@
1
1
  import { Option } from "./Option.js";
2
2
  import { Result } from "./Result.js";
3
+ const _notAsked = { kind: "NotAsked" };
4
+ const _loading = { kind: "Loading" };
3
5
  export var RemoteData;
4
6
  (function (RemoteData) {
5
7
  /**
6
8
  * Creates a NotAsked RemoteData.
7
9
  */
8
- RemoteData.notAsked = () => ({ kind: "NotAsked" });
10
+ RemoteData.notAsked = () => _notAsked;
9
11
  /**
10
12
  * Creates a Loading RemoteData.
11
13
  */
12
- RemoteData.loading = () => ({ kind: "Loading" });
14
+ RemoteData.loading = () => _loading;
13
15
  /**
14
16
  * Creates a Failure RemoteData with the given error.
15
17
  */
@@ -159,9 +161,9 @@ export var RemoteData;
159
161
  *
160
162
  * @example
161
163
  * ```ts
162
- * pipe(RemoteData.success(5), RemoteData.getOrElse(0)); // 5
163
- * pipe(RemoteData.loading(), RemoteData.getOrElse(0)); // 0
164
- * pipe(RemoteData.loading<string, number>(), RemoteData.getOrElse(null)); // null — typed as number | null
164
+ * pipe(RemoteData.success(5), RemoteData.getOrElse(() => 0)); // 5
165
+ * pipe(RemoteData.loading(), RemoteData.getOrElse(() => 0)); // 0
166
+ * pipe(RemoteData.loading<string, number>(), RemoteData.getOrElse(() => null)); // null — typed as number | null
165
167
  * ```
166
168
  */
167
169
  RemoteData.getOrElse = (defaultValue) => (data) => RemoteData.isSuccess(data) ? data.value : defaultValue();
@@ -205,7 +207,5 @@ export var RemoteData;
205
207
  * ); // Ok(42)
206
208
  * ```
207
209
  */
208
- RemoteData.toResult = (onNotReady) => (data) => RemoteData.isSuccess(data)
209
- ? Result.ok(data.value)
210
- : Result.err(RemoteData.isFailure(data) ? data.error : onNotReady());
210
+ RemoteData.toResult = (onNotReady) => (data) => RemoteData.isSuccess(data) ? Result.ok(data.value) : Result.err(RemoteData.isFailure(data) ? data.error : onNotReady());
211
211
  })(RemoteData || (RemoteData = {}));
@@ -39,15 +39,6 @@ export var TaskValidation;
39
39
  * Transforms the success value inside a TaskValidation.
40
40
  */
41
41
  TaskValidation.map = (f) => (data) => Task.map(Validation.map(f))(data);
42
- /**
43
- * Chains TaskValidation computations. If the first is Valid, passes the value
44
- * to f. If the first is Invalid, propagates the errors.
45
- *
46
- * Note: chain short-circuits on first error. Use ap to accumulate errors.
47
- */
48
- TaskValidation.chain = (f) => (data) => Task.chain((validation) => Validation.isValid(validation)
49
- ? f(validation.value)
50
- : Task.resolve(Validation.invalidAll(validation.errors)))(data);
51
42
  /**
52
43
  * Applies a function wrapped in a TaskValidation to a value wrapped in a
53
44
  * TaskValidation. Both Tasks run in parallel and errors from both sides
@@ -97,9 +88,41 @@ export var TaskValidation;
97
88
  TaskValidation.tap = (f) => (data) => Task.map(Validation.tap(f))(data);
98
89
  /**
99
90
  * Recovers from an Invalid state by providing a fallback TaskValidation.
91
+ * The fallback receives the accumulated error list so callers can inspect which errors occurred.
100
92
  * The fallback can produce a different success type, widening the result to `TaskValidation<E, A | B>`.
101
93
  */
102
- TaskValidation.recover = (fallback) => (data) => Task.chain((validation) => Validation.isValid(validation)
103
- ? Task.resolve(validation)
104
- : fallback())(data);
94
+ TaskValidation.recover = (fallback) => (data) => Task.chain((validation) => Validation.isValid(validation) ? Task.resolve(validation) : fallback(validation.errors))(data);
95
+ /**
96
+ * Runs two TaskValidations concurrently and combines their results into a tuple.
97
+ * If both are Valid, returns Valid with both values. If either fails, accumulates
98
+ * errors from both sides.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * await TaskValidation.product(
103
+ * validateName(form.name),
104
+ * validateAge(form.age),
105
+ * )(); // Valid(["Alice", 30]) or Invalid([...errors])
106
+ * ```
107
+ */
108
+ TaskValidation.product = (first, second) => Task.from(() => Promise.all([
109
+ Deferred.toPromise(first()),
110
+ Deferred.toPromise(second()),
111
+ ]).then(([va, vb]) => Validation.product(va, vb)));
112
+ /**
113
+ * Runs all TaskValidations concurrently and collects results.
114
+ * If all are Valid, returns Valid with all values as an array.
115
+ * If any fail, returns Invalid with all accumulated errors.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * await TaskValidation.productAll([
120
+ * validateName(form.name),
121
+ * validateEmail(form.email),
122
+ * validateAge(form.age),
123
+ * ])(); // Valid([name, email, age]) or Invalid([...all errors])
124
+ * ```
125
+ */
126
+ TaskValidation.productAll = (data) => Task.from(() => Promise.all(data.map((t) => Deferred.toPromise(t())))
127
+ .then((results) => Validation.productAll(results)));
105
128
  })(TaskValidation || (TaskValidation = {}));
@@ -119,9 +119,9 @@ export var Validation;
119
119
  *
120
120
  * @example
121
121
  * ```ts
122
- * pipe(Validation.valid(5), Validation.getOrElse(0)); // 5
123
- * pipe(Validation.invalid("oops"), Validation.getOrElse(0)); // 0
124
- * pipe(Validation.invalid("oops"), Validation.getOrElse(null)); // null — typed as number | null
122
+ * pipe(Validation.valid(5), Validation.getOrElse(() => 0)); // 5
123
+ * pipe(Validation.invalid("oops"), Validation.getOrElse(() => 0)); // 0
124
+ * pipe(Validation.invalid("oops"), Validation.getOrElse(() => null)); // null — typed as number | null
125
125
  * ```
126
126
  */
127
127
  Validation.getOrElse = (defaultValue) => (data) => Validation.isValid(data) ? data.value : defaultValue();
@@ -1,11 +1,9 @@
1
- export * from "./Arr.js";
2
1
  export * from "./Logged.js";
3
2
  export * from "./Deferred.js";
4
3
  export * from "./Lens.js";
5
4
  export * from "./Option.js";
6
5
  export * from "./Reader.js";
7
6
  export * from "./Optional.js";
8
- export * from "./Rec.js";
9
7
  export * from "./Predicate.js";
10
8
  export * from "./Refinement.js";
11
9
  export * from "./RemoteData.js";
@@ -1,7 +1,7 @@
1
- import { Deferred } from "./Deferred.js";
2
- import { Option } from "./Option.js";
3
- import { Result } from "./Result.js";
4
- import { Task } from "./Task.js";
1
+ import { Deferred } from "../Core/Deferred.js";
2
+ import { Option } from "../Core/Option.js";
3
+ import { Result } from "../Core/Result.js";
4
+ import { Task } from "../Core/Task.js";
5
5
  import { isNonEmptyList } from "../Types/NonEmptyList.js";
6
6
  /**
7
7
  * Functional array utilities that compose well with pipe.
@@ -110,7 +110,13 @@ export var Arr;
110
110
  * pipe([1, 2, 3], Arr.map(n => n * 2)); // [2, 4, 6]
111
111
  * ```
112
112
  */
113
- Arr.map = (f) => (data) => data.map(f);
113
+ Arr.map = (f) => (data) => {
114
+ const n = data.length;
115
+ const result = new Array(n);
116
+ for (let i = 0; i < n; i++)
117
+ result[i] = f(data[i]);
118
+ return result;
119
+ };
114
120
  /**
115
121
  * Filters elements that satisfy the predicate.
116
122
  *
@@ -119,7 +125,15 @@ export var Arr;
119
125
  * pipe([1, 2, 3, 4], Arr.filter(n => n % 2 === 0)); // [2, 4]
120
126
  * ```
121
127
  */
122
- Arr.filter = (predicate) => (data) => data.filter(predicate);
128
+ Arr.filter = (predicate) => (data) => {
129
+ const n = data.length;
130
+ const result = [];
131
+ for (let i = 0; i < n; i++) {
132
+ if (predicate(data[i]))
133
+ result.push(data[i]);
134
+ }
135
+ return result;
136
+ };
123
137
  /**
124
138
  * Splits an array into two groups based on a predicate.
125
139
  * First group contains elements that satisfy the predicate,
@@ -213,9 +227,9 @@ export var Arr;
213
227
  */
214
228
  Arr.zip = (other) => (data) => {
215
229
  const len = Math.min(data.length, other.length);
216
- const result = [];
230
+ const result = new Array(len);
217
231
  for (let i = 0; i < len; i++) {
218
- result.push([data[i], other[i]]);
232
+ result[i] = [data[i], other[i]];
219
233
  }
220
234
  return result;
221
235
  };
@@ -229,9 +243,9 @@ export var Arr;
229
243
  */
230
244
  Arr.zipWith = (f) => (other) => (data) => {
231
245
  const len = Math.min(data.length, other.length);
232
- const result = [];
246
+ const result = new Array(len);
233
247
  for (let i = 0; i < len; i++) {
234
- result.push(f(data[i], other[i]));
248
+ result[i] = f(data[i], other[i]);
235
249
  }
236
250
  return result;
237
251
  };
@@ -286,7 +300,17 @@ export var Arr;
286
300
  * pipe([1, 2, 3], Arr.flatMap(n => [n, n * 10])); // [1, 10, 2, 20, 3, 30]
287
301
  * ```
288
302
  */
289
- Arr.flatMap = (f) => (data) => [].concat(...data.map(f));
303
+ Arr.flatMap = (f) => (data) => {
304
+ const n = data.length;
305
+ const result = [];
306
+ for (let i = 0; i < n; i++) {
307
+ const chunk = f(data[i]);
308
+ const m = chunk.length;
309
+ for (let j = 0; j < m; j++)
310
+ result.push(chunk[j]);
311
+ }
312
+ return result;
313
+ };
290
314
  /**
291
315
  * Reduces an array from the left.
292
316
  *
@@ -313,12 +337,13 @@ export var Arr;
313
337
  * ```
314
338
  */
315
339
  Arr.traverse = (f) => (data) => {
316
- const result = [];
317
- for (const a of data) {
318
- const mapped = f(a);
319
- if (Option.isNone(mapped))
340
+ const n = data.length;
341
+ const result = new Array(n);
342
+ for (let i = 0; i < n; i++) {
343
+ const mapped = f(data[i]);
344
+ if (mapped.kind === "None")
320
345
  return Option.none();
321
- result.push(mapped.value);
346
+ result[i] = mapped.value;
322
347
  }
323
348
  return Option.some(result);
324
349
  };
@@ -335,12 +360,13 @@ export var Arr;
335
360
  * ```
336
361
  */
337
362
  Arr.traverseResult = (f) => (data) => {
338
- const result = [];
339
- for (const a of data) {
340
- const mapped = f(a);
341
- if (Result.isErr(mapped))
363
+ const n = data.length;
364
+ const result = new Array(n);
365
+ for (let i = 0; i < n; i++) {
366
+ const mapped = f(data[i]);
367
+ if (mapped.kind === "Error")
342
368
  return mapped;
343
- result.push(mapped.value);
369
+ result[i] = mapped.value;
344
370
  }
345
371
  return Result.ok(result);
346
372
  };
@@ -427,7 +453,13 @@ export var Arr;
427
453
  * pipe([1, 2, 3], Arr.some(n => n > 2)); // true
428
454
  * ```
429
455
  */
430
- Arr.some = (predicate) => (data) => data.some(predicate);
456
+ Arr.some = (predicate) => (data) => {
457
+ const n = data.length;
458
+ for (let i = 0; i < n; i++)
459
+ if (predicate(data[i]))
460
+ return true;
461
+ return false;
462
+ };
431
463
  /**
432
464
  * Returns true if all elements satisfy the predicate.
433
465
  *
@@ -436,7 +468,13 @@ export var Arr;
436
468
  * pipe([1, 2, 3], Arr.every(n => n > 0)); // true
437
469
  * ```
438
470
  */
439
- Arr.every = (predicate) => (data) => data.every(predicate);
471
+ Arr.every = (predicate) => (data) => {
472
+ const n = data.length;
473
+ for (let i = 0; i < n; i++)
474
+ if (!predicate(data[i]))
475
+ return false;
476
+ return true;
477
+ };
440
478
  /**
441
479
  * Reverses an array. Returns a new array.
442
480
  *
@@ -495,4 +533,38 @@ export var Arr;
495
533
  i++;
496
534
  return data.slice(i);
497
535
  };
536
+ /**
537
+ * Like `reduce`, but returns every intermediate accumulator as an array.
538
+ * The initial value is not included — the output has the same length as the input.
539
+ *
540
+ * @example
541
+ * ```ts
542
+ * pipe([1, 2, 3], Arr.scan(0, (acc, n) => acc + n)); // [1, 3, 6]
543
+ * ```
544
+ */
545
+ Arr.scan = (initial, f) => (data) => {
546
+ const n = data.length;
547
+ const result = new Array(n);
548
+ let acc = initial;
549
+ for (let i = 0; i < n; i++) {
550
+ acc = f(acc, data[i]);
551
+ result[i] = acc;
552
+ }
553
+ return result;
554
+ };
555
+ /**
556
+ * Splits an array at an index into a `[before, after]` tuple.
557
+ * Negative indices clamp to 0; indices beyond the array length clamp to the end.
558
+ *
559
+ * @example
560
+ * ```ts
561
+ * pipe([1, 2, 3, 4], Arr.splitAt(2)); // [[1, 2], [3, 4]]
562
+ * pipe([1, 2, 3], Arr.splitAt(0)); // [[], [1, 2, 3]]
563
+ * pipe([1, 2, 3], Arr.splitAt(10)); // [[1, 2, 3], []]
564
+ * ```
565
+ */
566
+ Arr.splitAt = (index) => (data) => {
567
+ const i = Math.max(0, index);
568
+ return [data.slice(0, i), data.slice(i)];
569
+ };
498
570
  })(Arr || (Arr = {}));
@@ -0,0 +1,124 @@
1
+ import { Option } from "../Core/Option.js";
2
+ /**
3
+ * Number utilities for common operations. All transformation functions are data-last
4
+ * and curried so they compose naturally with `pipe` and `Arr.map`.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { Num } from "@nlozgachev/pipelined/utils";
9
+ * import { pipe } from "@nlozgachev/pipelined/composition";
10
+ *
11
+ * pipe(
12
+ * Num.range(1, 6),
13
+ * Arr.map(Num.multiply(2)),
14
+ * Arr.filter(Num.between(4, 8))
15
+ * ); // [4, 6, 8]
16
+ * ```
17
+ */
18
+ export var Num;
19
+ (function (Num) {
20
+ /**
21
+ * Generates an array of numbers from `from` to `to` (both inclusive),
22
+ * stepping by `step` (default `1`). If `step` is negative or zero, or `from > to`,
23
+ * returns an empty array. When `step` does not land exactly on `to`, the last value
24
+ * is the largest reachable value that does not exceed `to`.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * Num.range(0, 5); // [0, 1, 2, 3, 4, 5]
29
+ * Num.range(0, 10, 2); // [0, 2, 4, 6, 8, 10]
30
+ * Num.range(0, 9, 2); // [0, 2, 4, 6, 8]
31
+ * Num.range(5, 0); // []
32
+ * Num.range(3, 3); // [3]
33
+ * ```
34
+ */
35
+ Num.range = (from, to, step = 1) => {
36
+ if (step <= 0 || from > to)
37
+ return [];
38
+ const count = Math.floor((to - from) / step) + 1;
39
+ const result = new Array(count);
40
+ for (let i = 0; i < count; i++) {
41
+ result[i] = from + i * step;
42
+ }
43
+ return result;
44
+ };
45
+ /**
46
+ * Clamps a number between `min` and `max` (both inclusive).
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * pipe(150, Num.clamp(0, 100)); // 100
51
+ * pipe(-5, Num.clamp(0, 100)); // 0
52
+ * pipe(42, Num.clamp(0, 100)); // 42
53
+ * ```
54
+ */
55
+ Num.clamp = (min, max) => (n) => Math.min(Math.max(n, min), max);
56
+ /**
57
+ * Returns `true` when the number is between `min` and `max` (both inclusive).
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * pipe(5, Num.between(1, 10)); // true
62
+ * pipe(0, Num.between(1, 10)); // false
63
+ * pipe(10, Num.between(1, 10)); // true
64
+ * ```
65
+ */
66
+ Num.between = (min, max) => (n) => n >= min && n <= max;
67
+ /**
68
+ * Parses a string as a number. Returns `None` when the result is `NaN`.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * Num.parse("42"); // Some(42)
73
+ * Num.parse("3.14"); // Some(3.14)
74
+ * Num.parse("abc"); // None
75
+ * Num.parse(""); // None
76
+ * ```
77
+ */
78
+ Num.parse = (s) => {
79
+ if (s.trim() === "")
80
+ return Option.none();
81
+ const n = Number(s);
82
+ return isNaN(n) ? Option.none() : Option.some(n);
83
+ };
84
+ /**
85
+ * Adds `b` to a number. Data-last: use in `pipe` or `Arr.map`.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * pipe(5, Num.add(3)); // 8
90
+ * pipe([1, 2, 3], Arr.map(Num.add(10))); // [11, 12, 13]
91
+ * ```
92
+ */
93
+ Num.add = (b) => (a) => a + b;
94
+ /**
95
+ * Subtracts `b` from a number. Data-last: `subtract(b)(a)` = `a - b`.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * pipe(10, Num.subtract(3)); // 7
100
+ * pipe([5, 10, 15], Arr.map(Num.subtract(2))); // [3, 8, 13]
101
+ * ```
102
+ */
103
+ Num.subtract = (b) => (a) => a - b;
104
+ /**
105
+ * Multiplies a number by `b`. Data-last: use in `pipe` or `Arr.map`.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * pipe(6, Num.multiply(7)); // 42
110
+ * pipe([1, 2, 3], Arr.map(Num.multiply(100))); // [100, 200, 300]
111
+ * ```
112
+ */
113
+ Num.multiply = (b) => (a) => a * b;
114
+ /**
115
+ * Divides a number by `b`. Data-last: `divide(b)(a)` = `a / b`.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * pipe(20, Num.divide(4)); // 5
120
+ * pipe([10, 20, 30], Arr.map(Num.divide(10))); // [1, 2, 3]
121
+ * ```
122
+ */
123
+ Num.divide = (b) => (a) => a / b;
124
+ })(Num || (Num = {}));
@@ -1,4 +1,4 @@
1
- import { Option } from "./Option.js";
1
+ import { Option } from "../Core/Option.js";
2
2
  /**
3
3
  * Functional record/object utilities that compose well with pipe.
4
4
  * All functions are data-last and curried where applicable.
@@ -23,9 +23,11 @@ export var Rec;
23
23
  * ```
24
24
  */
25
25
  Rec.map = (f) => (data) => {
26
+ const keys = Object.keys(data);
27
+ const vals = Object.values(data);
26
28
  const result = {};
27
- for (const key of Object.keys(data)) {
28
- result[key] = f(data[key]);
29
+ for (let i = 0; i < keys.length; i++) {
30
+ result[keys[i]] = f(vals[i]);
29
31
  }
30
32
  return result;
31
33
  };
@@ -39,9 +41,11 @@ export var Rec;
39
41
  * ```
40
42
  */
41
43
  Rec.mapWithKey = (f) => (data) => {
44
+ const keys = Object.keys(data);
45
+ const vals = Object.values(data);
42
46
  const result = {};
43
- for (const key of Object.keys(data)) {
44
- result[key] = f(key, data[key]);
47
+ for (let i = 0; i < keys.length; i++) {
48
+ result[keys[i]] = f(keys[i], vals[i]);
45
49
  }
46
50
  return result;
47
51
  };
@@ -54,10 +58,12 @@ export var Rec;
54
58
  * ```
55
59
  */
56
60
  Rec.filter = (predicate) => (data) => {
61
+ const keys = Object.keys(data);
62
+ const vals = Object.values(data);
57
63
  const result = {};
58
- for (const key of Object.keys(data)) {
59
- if (predicate(data[key]))
60
- result[key] = data[key];
64
+ for (let i = 0; i < keys.length; i++) {
65
+ if (predicate(vals[i]))
66
+ result[keys[i]] = vals[i];
61
67
  }
62
68
  return result;
63
69
  };
@@ -71,10 +77,12 @@ export var Rec;
71
77
  * ```
72
78
  */
73
79
  Rec.filterWithKey = (predicate) => (data) => {
80
+ const keys = Object.keys(data);
81
+ const vals = Object.values(data);
74
82
  const result = {};
75
- for (const key of Object.keys(data)) {
76
- if (predicate(key, data[key]))
77
- result[key] = data[key];
83
+ for (let i = 0; i < keys.length; i++) {
84
+ if (predicate(keys[i], vals[i]))
85
+ result[keys[i]] = vals[i];
78
86
  }
79
87
  return result;
80
88
  };
@@ -164,4 +172,44 @@ export var Rec;
164
172
  * Returns the number of keys in a record.
165
173
  */
166
174
  Rec.size = (data) => Object.keys(data).length;
175
+ /**
176
+ * Transforms each key while preserving values.
177
+ * If two keys map to the same new key, the last one wins.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * pipe({ firstName: "Alice", lastName: "Smith" }, Rec.mapKeys(k => k.toUpperCase()));
182
+ * // { FIRSTNAME: "Alice", LASTNAME: "Smith" }
183
+ * ```
184
+ */
185
+ Rec.mapKeys = (f) => (data) => {
186
+ const keys = Object.keys(data);
187
+ const vals = Object.values(data);
188
+ const result = {};
189
+ for (let i = 0; i < keys.length; i++) {
190
+ result[f(keys[i])] = vals[i];
191
+ }
192
+ return result;
193
+ };
194
+ /**
195
+ * Removes all `None` values from a `Record<string, Option<A>>`, returning a plain `Record<string, A>`.
196
+ * Useful when building records from fallible lookups.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * Rec.compact({ a: Option.some(1), b: Option.none(), c: Option.some(3) });
201
+ * // { a: 1, c: 3 }
202
+ * ```
203
+ */
204
+ Rec.compact = (data) => {
205
+ const keys = Object.keys(data);
206
+ const vals = Object.values(data);
207
+ const result = {};
208
+ for (let i = 0; i < keys.length; i++) {
209
+ const v = vals[i];
210
+ if (v.kind === "Some")
211
+ result[keys[i]] = v.value;
212
+ }
213
+ return result;
214
+ };
167
215
  })(Rec || (Rec = {}));