@nlozgachev/pipekit 0.1.6

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 (100) hide show
  1. package/README.md +182 -0
  2. package/esm/mod.js +3 -0
  3. package/esm/package.json +3 -0
  4. package/esm/src/Composition/compose.js +3 -0
  5. package/esm/src/Composition/curry.js +42 -0
  6. package/esm/src/Composition/flip.js +20 -0
  7. package/esm/src/Composition/flow.js +8 -0
  8. package/esm/src/Composition/fn.js +85 -0
  9. package/esm/src/Composition/index.js +10 -0
  10. package/esm/src/Composition/memoize.js +66 -0
  11. package/esm/src/Composition/not.js +25 -0
  12. package/esm/src/Composition/pipe.js +3 -0
  13. package/esm/src/Composition/tap.js +33 -0
  14. package/esm/src/Composition/uncurry.js +32 -0
  15. package/esm/src/Core/Arr.js +461 -0
  16. package/esm/src/Core/InternalTypes.js +1 -0
  17. package/esm/src/Core/Option.js +195 -0
  18. package/esm/src/Core/Rec.js +167 -0
  19. package/esm/src/Core/RemoteData.js +210 -0
  20. package/esm/src/Core/Result.js +173 -0
  21. package/esm/src/Core/Task.js +108 -0
  22. package/esm/src/Core/TaskResult.js +63 -0
  23. package/esm/src/Core/Validation.js +215 -0
  24. package/esm/src/Core/index.js +8 -0
  25. package/esm/src/Types/NonEmptyList.js +14 -0
  26. package/esm/src/Types/index.js +1 -0
  27. package/package.json +60 -0
  28. package/script/mod.js +19 -0
  29. package/script/package.json +3 -0
  30. package/script/src/Composition/compose.js +6 -0
  31. package/script/src/Composition/curry.js +48 -0
  32. package/script/src/Composition/flip.js +24 -0
  33. package/script/src/Composition/flow.js +11 -0
  34. package/script/src/Composition/fn.js +98 -0
  35. package/script/src/Composition/index.js +26 -0
  36. package/script/src/Composition/memoize.js +71 -0
  37. package/script/src/Composition/not.js +29 -0
  38. package/script/src/Composition/pipe.js +6 -0
  39. package/script/src/Composition/tap.js +37 -0
  40. package/script/src/Composition/uncurry.js +38 -0
  41. package/script/src/Core/Arr.js +464 -0
  42. package/script/src/Core/InternalTypes.js +2 -0
  43. package/script/src/Core/Option.js +198 -0
  44. package/script/src/Core/Rec.js +170 -0
  45. package/script/src/Core/RemoteData.js +213 -0
  46. package/script/src/Core/Result.js +176 -0
  47. package/script/src/Core/Task.js +111 -0
  48. package/script/src/Core/TaskResult.js +66 -0
  49. package/script/src/Core/Validation.js +218 -0
  50. package/script/src/Core/index.js +24 -0
  51. package/script/src/Types/NonEmptyList.js +18 -0
  52. package/script/src/Types/index.js +17 -0
  53. package/types/mod.d.ts +4 -0
  54. package/types/mod.d.ts.map +1 -0
  55. package/types/src/Composition/compose.d.ts +33 -0
  56. package/types/src/Composition/compose.d.ts.map +1 -0
  57. package/types/src/Composition/curry.d.ts +43 -0
  58. package/types/src/Composition/curry.d.ts.map +1 -0
  59. package/types/src/Composition/flip.d.ts +21 -0
  60. package/types/src/Composition/flip.d.ts.map +1 -0
  61. package/types/src/Composition/flow.d.ts +56 -0
  62. package/types/src/Composition/flow.d.ts.map +1 -0
  63. package/types/src/Composition/fn.d.ts +76 -0
  64. package/types/src/Composition/fn.d.ts.map +1 -0
  65. package/types/src/Composition/index.d.ts +11 -0
  66. package/types/src/Composition/index.d.ts.map +1 -0
  67. package/types/src/Composition/memoize.d.ts +46 -0
  68. package/types/src/Composition/memoize.d.ts.map +1 -0
  69. package/types/src/Composition/not.d.ts +26 -0
  70. package/types/src/Composition/not.d.ts.map +1 -0
  71. package/types/src/Composition/pipe.d.ts +56 -0
  72. package/types/src/Composition/pipe.d.ts.map +1 -0
  73. package/types/src/Composition/tap.d.ts +31 -0
  74. package/types/src/Composition/tap.d.ts.map +1 -0
  75. package/types/src/Composition/uncurry.d.ts +54 -0
  76. package/types/src/Composition/uncurry.d.ts.map +1 -0
  77. package/types/src/Core/Arr.d.ts +355 -0
  78. package/types/src/Core/Arr.d.ts.map +1 -0
  79. package/types/src/Core/InternalTypes.d.ts +14 -0
  80. package/types/src/Core/InternalTypes.d.ts.map +1 -0
  81. package/types/src/Core/Option.d.ts +214 -0
  82. package/types/src/Core/Option.d.ts.map +1 -0
  83. package/types/src/Core/Rec.d.ts +121 -0
  84. package/types/src/Core/Rec.d.ts.map +1 -0
  85. package/types/src/Core/RemoteData.d.ts +196 -0
  86. package/types/src/Core/RemoteData.d.ts.map +1 -0
  87. package/types/src/Core/Result.d.ts +185 -0
  88. package/types/src/Core/Result.d.ts.map +1 -0
  89. package/types/src/Core/Task.d.ts +125 -0
  90. package/types/src/Core/Task.d.ts.map +1 -0
  91. package/types/src/Core/TaskResult.d.ts +78 -0
  92. package/types/src/Core/TaskResult.d.ts.map +1 -0
  93. package/types/src/Core/Validation.d.ts +217 -0
  94. package/types/src/Core/Validation.d.ts.map +1 -0
  95. package/types/src/Core/index.d.ts +9 -0
  96. package/types/src/Core/index.d.ts.map +1 -0
  97. package/types/src/Types/NonEmptyList.d.ts +29 -0
  98. package/types/src/Types/NonEmptyList.d.ts.map +1 -0
  99. package/types/src/Types/index.d.ts +2 -0
  100. package/types/src/Types/index.d.ts.map +1 -0
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # @nlozgachev/pipekit
2
+
3
+ [![npm](https://img.shields.io/npm/v/@nlozgachev/pipekit?style=for-the-badge&color=000&logo=npm&label&logoColor=fff)](https://www.npmjs.com/package/@nlozgachev/pipekit)[![JSR Version](https://img.shields.io/jsr/v/@nlozgachev/pipekit?style=for-the-badge&color=000&logo=jsr&label&logoColor=fff)](https://jsr.io/@nlozgachev/pipekit)[![TypeScript](https://img.shields.io/badge/-0?style=for-the-badge&color=000&logo=typescript&label&logoColor=fff)](https://www.typescriptlang.org)[![Deno](https://img.shields.io/badge/-0?style=for-the-badge&color=000&logo=Deno&label&logoColor=fff)](https://deno.com)
4
+
5
+ A functional programming toolkit for TypeScript that speaks your language.
6
+
7
+ ## What is this?
8
+
9
+ A collection of types and utilities that help you write safer, composition-centric, more predictable TypeScript. If you've ever dealt with `null` checks scattered across your codebase, nested `try/catch` blocks, or `{ data: T | null; loading: boolean; error: Error | null }` — this library gives you better tools for all of that.
10
+
11
+ ## Do I need to know functional programming to use this?
12
+
13
+ No. The library avoids FP-specific jargon wherever possible. You won't find `Monad`, `Functor`, or `Applicative` in the API. Instead, you'll work with names that describe what they do:
14
+
15
+ - `Option` — a value that might not exist
16
+ - `Result` — an operation that can succeed or fail
17
+ - `map` — transform a value inside a container
18
+ - `chain` — sequence operations that might fail
19
+ - `match` — handle each case explicitly
20
+
21
+ You can start using these right away and learn the underlying concepts as you go.
22
+
23
+ ## What's included?
24
+
25
+ ### pipekit/Core
26
+
27
+ Each of these is both a TypeScript type and a module of functions for working with values of that type — constructors, transformations, and ways to extract a value back out.
28
+
29
+ - **`Option<A>`** — a value that might not exist. Replaces `T | null | undefined`. Key operations: `fromNullable`, `map`, `chain`, `filter`, `match`, `getOrElse`, `recover`.
30
+ - **`Result<E, A>`** — an operation that succeeds with `A` or fails with `E`. Replaces `try/catch`. Key operations: `tryCatch`, `map`, `mapError`, `chain`, `match`, `getOrElse`, `recover`.
31
+ - **`Validation<E, A>`** — like `Result`, but accumulates **all** errors instead of stopping at the first. Built for form validation. Key operations: `combine`, `combineAll`, `ap`, `map`, `match`.
32
+ - **`Task<A>`** — a lazy async operation that doesn't run until called. Key operations: `from`, `map`, `chain`, `all`, `delay`.
33
+ - **`TaskResult<E, A>`** — a lazy async operation that can fail. `Task` + `Result` combined. Key operations: `tryCatch`, `map`, `mapError`, `chain`, `match`, `recover`.
34
+ - **`RemoteData<E, A>`** — models the four states of a data fetch: `NotAsked`, `Loading`, `Failure`, `Success`. Replaces `{ data, loading, error }` flag soup. Key operations: `notAsked`, `loading`, `failure`, `success`, `map`, `match`, `toResult`.
35
+ - **`Arr`** — array operations that return `Option` instead of throwing or returning `undefined`. Operations: `head`, `last`, `findFirst`, `findLast`, `partition`, `groupBy`, `zip`, `traverse`, and more.
36
+ - **`Rec`** — record/object operations. Operations: `lookup`, `map`, `filter`, `pick`, `omit`, `merge`, and more.
37
+
38
+ ### pipekit/Types
39
+
40
+ - **`NonEmptyList<A>`** — an array guaranteed to have at least one element.
41
+
42
+ ### pipekit/Composition
43
+
44
+ - **`pipe`** — pass a value through a series of functions, left to right.
45
+ - **`flow`** — compose functions into a reusable pipeline (like `pipe` without an initial value).
46
+ - **`compose`** — compose functions right to left (traditional mathematical composition).
47
+ - **`curry` / `uncurry`** — convert between multi-argument and single-argument functions.
48
+ - **`tap`** — run a side effect (like logging) without breaking the pipeline.
49
+ - **`memoize`** — cache function results.
50
+ - **`identity`**, **`constant`**, **`not`**, **`and`**, **`or`**, **`once`**, **`flip`** — common function utilities.
51
+
52
+ ## What does "composition-centric" mean?
53
+
54
+ Everything in the library is designed to work with `pipe` — a function that passes a value through a series of transformations, top to bottom. The alternative is nesting calls inside each other, which reads inside-out:
55
+
56
+ ```ts
57
+ import { Option } from "@nlozgachev/pipekit/Core";
58
+ import { pipe } from "@nlozgachev/pipekit/Composition";
59
+
60
+ // Without pipe: execution order is the reverse of reading order
61
+ const userName = Option.getOrElse(
62
+ Option.map(
63
+ Option.fromNullable(users.get("123")),
64
+ u => u.name
65
+ ),
66
+ "Anonymous"
67
+ );
68
+
69
+ // With pipe: reads top to bottom, matches execution order
70
+ const userName = pipe(
71
+ users.get("123"), // User | undefined
72
+ Option.fromNullable, // Option<User>
73
+ Option.map(u => u.name), // Option<string>
74
+ Option.getOrElse("Anonymous") // string
75
+ );
76
+ ```
77
+
78
+ No method chaining, no class hierarchies. Just functions that connect together.
79
+
80
+ ## What does "data-last" mean and why should I care?
81
+
82
+ Every operation takes the data it operates on as the **last** argument. This means you can partially apply them — get a function back without providing data yet — which makes `flow` work naturally.
83
+
84
+ ```ts
85
+ import { Option } from "@nlozgachev/pipekit/Core";
86
+ import { flow } from "@nlozgachev/pipekit/Composition";
87
+
88
+ // Data-first: can't partially apply, so you're stuck writing wrapper functions
89
+ function formatName(user: User | null): string {
90
+ const opt = Option.fromNullable(user);
91
+ const name = Option.map(opt, u => u.name);
92
+ return Option.getOrElse(name, "Anonymous");
93
+ }
94
+
95
+ users.map(u => formatName(u)); // needs an arrow function wrapper
96
+
97
+ // Data-last: operations are curried, so flow connects them directly
98
+ const formatName = flow(
99
+ Option.fromNullable<User>,
100
+ Option.map(u => u.name),
101
+ Option.getOrElse("Anonymous")
102
+ );
103
+
104
+ users.map(formatName); // no wrapper — it's already a function of User
105
+ ```
106
+
107
+ ## How does this help me write safer code?
108
+
109
+ The core types make invalid states unrepresentable. Instead of stacking null checks and hoping you handled every branch, the shape of the code reflects the shape of the data:
110
+
111
+ ```ts
112
+ // Before: null handling that doesn't scale
113
+ function getDisplayCity(userId: string): string {
114
+ const user = getUser(userId);
115
+ if (user === null) return "UNKNOWN";
116
+ if (user.address === null) return "UNKNOWN";
117
+ if (user.address.city === null) return "UNKNOWN";
118
+ return user.address.city.toUpperCase();
119
+ }
120
+
121
+ // After: flat, readable, same guarantees
122
+ function getDisplayCity(userId: string): string {
123
+ return pipe(
124
+ getUser(userId), // User | null
125
+ Option.fromNullable, // Option<User>
126
+ Option.chain(u => Option.fromNullable(u.address)), // Option<Address>
127
+ Option.chain(a => Option.fromNullable(a.city)), // Option<string>
128
+ Option.map(c => c.toUpperCase()), // Option<string>
129
+ Option.getOrElse("UNKNOWN") // string
130
+ );
131
+ }
132
+ ```
133
+
134
+ The same idea applies to error handling with `Result`, form validation with `Validation`, async operations with `Task` and `TaskResult`, and loading states with `RemoteData`.
135
+
136
+
137
+
138
+ ## How do I install it?
139
+
140
+ ```sh
141
+ # Deno
142
+ deno add jsr:@nlozgachev/pipekit
143
+
144
+ # npm / pnpm / yarn / bun
145
+ npm add @nlozgachev/pipekit
146
+ ```
147
+
148
+ ## How do I get started?
149
+
150
+ Start with `pipe` and `Option`. These two cover the most common pain point — dealing with values that might not exist:
151
+
152
+ ```ts
153
+ import { Option } from "@nlozgachev/pipekit/Core";
154
+ import { pipe } from "@nlozgachev/pipekit/Composition";
155
+
156
+ // Read a user's preferred language from their settings, fall back to the app default
157
+ const language = pipe(
158
+ userSettings.get(userId), // UserSettings | undefined
159
+ Option.fromNullable, // Option<UserSettings>
160
+ Option.map(s => s.language), // Option<string>
161
+ Option.getOrElse(DEFAULT_LANGUAGE) // string
162
+ );
163
+ ```
164
+
165
+ Once that feels natural, reach for `Result` when operations can fail with a meaningful error — parsing, network calls, database lookups:
166
+
167
+ ```ts
168
+ import { Result } from "@nlozgachev/pipekit/Core";
169
+
170
+ // Parse user input and look up the record — both steps can fail
171
+ const record = pipe(
172
+ parseId(rawInput), // Result<ParseError, number>
173
+ Result.chain(id => db.find(id)), // Result<ParseError | NotFoundError, Record>
174
+ Result.map(r => r.name), // Result<ParseError | NotFoundError, string>
175
+ );
176
+ ```
177
+
178
+ And `Validation` when you need to collect multiple errors at once, like form validation.
179
+
180
+ ## License
181
+
182
+ MIT
package/esm/mod.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./src/Composition/index.js";
2
+ export * from "./src/Core/index.js";
3
+ export * from "./src/Composition/index.js";
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,3 @@
1
+ export function compose(...fns) {
2
+ return (arg) => fns.reduceRight((acc, fn) => fn(acc), arg);
3
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Converts a multi-argument function into a curried function.
3
+ * The inverse of `uncurry`.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const add = (a: number, b: number) => a + b;
8
+ * const curriedAdd = curry(add);
9
+ * curriedAdd(1)(2); // 3
10
+ *
11
+ * // Partial application
12
+ * const addTen = curriedAdd(10);
13
+ * addTen(5); // 15
14
+ * ```
15
+ *
16
+ * @see {@link uncurry} for the inverse operation
17
+ * @see {@link curry3} for 3-argument functions
18
+ * @see {@link curry4} for 4-argument functions
19
+ */
20
+ export const curry = (f) => (a) => (b) => f(a, b);
21
+ /**
22
+ * Converts a 3-argument function into a curried function.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const add3 = (a: number, b: number, c: number) => a + b + c;
27
+ * const curriedAdd3 = curry3(add3);
28
+ * curriedAdd3(1)(2)(3); // 6
29
+ * ```
30
+ */
31
+ export const curry3 = (f) => (a) => (b) => (c) => f(a, b, c);
32
+ /**
33
+ * Converts a 4-argument function into a curried function.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const add4 = (a: number, b: number, c: number, d: number) => a + b + c + d;
38
+ * const curriedAdd4 = curry4(add4);
39
+ * curriedAdd4(1)(2)(3)(4); // 10
40
+ * ```
41
+ */
42
+ export const curry4 = (f) => (a) => (b) => (c) => (d) => f(a, b, c, d);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Flips the order of arguments for a curried binary function.
3
+ * Converts a data-last function to data-first.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * // Original data-last (for pipe)
8
+ * pipe(
9
+ * Option.of(5),
10
+ * Option.map(n => n * 2)
11
+ * ); // Some(10)
12
+ *
13
+ * // Flipped to data-first
14
+ * const mapFirst = flip(Option.map);
15
+ * mapFirst(Option.of(5))(n => n * 2); // Some(10)
16
+ * ```
17
+ *
18
+ * @see {@link uncurry} for converting curried functions to multi-argument functions
19
+ */
20
+ export const flip = (f) => (b) => (a) => f(a)(b);
@@ -0,0 +1,8 @@
1
+ export function flow(...fns) {
2
+ return (...args) => {
3
+ if (fns.length === 0)
4
+ return args[0];
5
+ const [first, ...rest] = fns;
6
+ return rest.reduce((acc, fn) => fn(acc), first(...args));
7
+ };
8
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Returns the value unchanged. The identity function.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * identity(42); // 42
7
+ * pipe(Option.of(5), Option.fold(() => 0, identity)); // 5
8
+ * ```
9
+ */
10
+ export const identity = (a) => a;
11
+ /**
12
+ * Creates a function that always returns the given value, ignoring its argument.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const always42 = constant(42);
17
+ * always42(); // 42
18
+ * [1, 2, 3].map(constant("x")); // ["x", "x", "x"]
19
+ * ```
20
+ */
21
+ export const constant = (a) => () => a;
22
+ /** Always returns `true`. */
23
+ export const constTrue = () => true;
24
+ /** Always returns `false`. */
25
+ export const constFalse = () => false;
26
+ /** Always returns `null`. */
27
+ export const constNull = () => null;
28
+ /** Always returns `undefined`. */
29
+ export const constUndefined = () => undefined;
30
+ /** Always returns `void`. */
31
+ export const constVoid = () => { };
32
+ /**
33
+ * Combines two predicates with logical AND.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const isPositive = (n: number) => n > 0;
38
+ * const isEven = (n: number) => n % 2 === 0;
39
+ * const isPositiveEven = and(isPositive, isEven);
40
+ *
41
+ * isPositiveEven(4); // true
42
+ * isPositiveEven(-2); // false
43
+ * isPositiveEven(3); // false
44
+ * ```
45
+ */
46
+ export const and = (p1, p2) => (...args) => p1(...args) && p2(...args);
47
+ /**
48
+ * Combines two predicates with logical OR.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const isNegative = (n: number) => n < 0;
53
+ * const isZero = (n: number) => n === 0;
54
+ * const isNonPositive = or(isNegative, isZero);
55
+ *
56
+ * isNonPositive(-1); // true
57
+ * isNonPositive(0); // true
58
+ * isNonPositive(1); // false
59
+ * ```
60
+ */
61
+ export const or = (p1, p2) => (...args) => p1(...args) || p2(...args);
62
+ /**
63
+ * Creates a function that executes at most once.
64
+ * Subsequent calls return the cached result from the first execution.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * let count = 0;
69
+ * const initOnce = once(() => { count++; return "initialized"; });
70
+ *
71
+ * initOnce(); // "initialized", count === 1
72
+ * initOnce(); // "initialized", count === 1 (not called again)
73
+ * ```
74
+ */
75
+ export const once = (f) => {
76
+ let called = false;
77
+ let result;
78
+ return () => {
79
+ if (!called) {
80
+ result = f();
81
+ called = true;
82
+ }
83
+ return result;
84
+ };
85
+ };
@@ -0,0 +1,10 @@
1
+ export * from "./compose.js";
2
+ export * from "./curry.js";
3
+ export * from "./flip.js";
4
+ export * from "./fn.js";
5
+ export * from "./flow.js";
6
+ export * from "./memoize.js";
7
+ export * from "./not.js";
8
+ export * from "./pipe.js";
9
+ export * from "./tap.js";
10
+ export * from "./uncurry.js";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Creates a memoized version of a function that caches results.
3
+ * Subsequent calls with the same argument return the cached result.
4
+ *
5
+ * By default, uses the argument directly as the cache key.
6
+ * For complex arguments, provide a custom `keyFn` to generate cache keys.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * // Basic usage
11
+ * const expensive = memoize((n: number) => {
12
+ * console.log("Computing...");
13
+ * return n * 2;
14
+ * });
15
+ *
16
+ * expensive(5); // logs "Computing...", returns 10
17
+ * expensive(5); // returns 10 (cached, no log)
18
+ * expensive(3); // logs "Computing...", returns 6
19
+ *
20
+ * // With custom key function for objects
21
+ * const fetchUser = memoize(
22
+ * (opts: { id: string }) => fetch(`/users/${opts.id}`),
23
+ * opts => opts.id
24
+ * );
25
+ * ```
26
+ */
27
+ export const memoize = (f, keyFn = (a) => a) => {
28
+ const cache = new Map();
29
+ return (a) => {
30
+ const key = keyFn(a);
31
+ if (cache.has(key)) {
32
+ return cache.get(key);
33
+ }
34
+ const result = f(a);
35
+ cache.set(key, result);
36
+ return result;
37
+ };
38
+ };
39
+ /**
40
+ * Creates a memoized version of a function using WeakMap.
41
+ * Only works with object arguments, but allows garbage collection
42
+ * of cached values when keys are no longer referenced.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const processUser = memoizeWeak((user: User) => {
47
+ * return expensiveOperation(user);
48
+ * });
49
+ *
50
+ * const user = { id: 1, name: "Alice" };
51
+ * processUser(user); // computed
52
+ * processUser(user); // cached
53
+ * // When `user` is garbage collected, cached result is too
54
+ * ```
55
+ */
56
+ export const memoizeWeak = (f) => {
57
+ const cache = new WeakMap();
58
+ return (a) => {
59
+ if (cache.has(a)) {
60
+ return cache.get(a);
61
+ }
62
+ const result = f(a);
63
+ cache.set(a, result);
64
+ return result;
65
+ };
66
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Negates a predicate function.
3
+ * Returns a new predicate that returns true when the original returns false, and vice versa.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const isEven = (n: number) => n % 2 === 0;
8
+ * const isOdd = not(isEven);
9
+ *
10
+ * isOdd(3); // true
11
+ * isOdd(4); // false
12
+ *
13
+ * // With array methods
14
+ * const numbers = [1, 2, 3, 4, 5];
15
+ * numbers.filter(not(isEven)); // [1, 3, 5]
16
+ *
17
+ * // In pipelines
18
+ * pipe(
19
+ * users,
20
+ * Array.filter(not(isAdmin)),
21
+ * Array.map(u => u.name)
22
+ * );
23
+ * ```
24
+ */
25
+ export const not = (predicate) => (...args) => !predicate(...args);
@@ -0,0 +1,3 @@
1
+ export function pipe(a, ...fns) {
2
+ return fns.reduce((acc, fn) => fn(acc), a);
3
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Executes a side effect function and returns the original value unchanged.
3
+ * Useful for logging, debugging, or other side effects within a pipeline.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * // Debugging a pipeline
8
+ * pipe(
9
+ * Option.of(5),
10
+ * tap(x => console.log("Before map:", x)),
11
+ * Option.map(n => n * 2),
12
+ * tap(x => console.log("After map:", x)),
13
+ * Option.getOrElse(0)
14
+ * );
15
+ * // logs: "Before map: { kind: 'Some', value: 5 }"
16
+ * // logs: "After map: { kind: 'Some', value: 10 }"
17
+ * // returns: 10
18
+ *
19
+ * // Collecting intermediate values
20
+ * const values: number[] = [];
21
+ * pipe(
22
+ * [1, 2, 3],
23
+ * arr => arr.map(n => n * 2),
24
+ * tap(arr => values.push(...arr))
25
+ * );
26
+ * ```
27
+ *
28
+ * @see {@link Option.tap} for Option-specific tap that only runs on Some
29
+ */
30
+ export const tap = (f) => (a) => {
31
+ f(a);
32
+ return a;
33
+ };
@@ -0,0 +1,32 @@
1
+ // deno-lint-ignore no-explicit-any
2
+ export function uncurry(f) {
3
+ // f.length determines the outer arity; inner.length determines the inner arity.
4
+ // The typed overloads guarantee these are 0, 1, or 2 total args.
5
+ // deno-lint-ignore no-explicit-any
6
+ return (...args) => {
7
+ const inner = f(...args.slice(0, f.length));
8
+ return inner.length === 0 ? inner() : inner(...args.slice(f.length));
9
+ };
10
+ }
11
+ /**
12
+ * Converts a curried 3-argument function into a multi-argument function.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const curriedAdd3 = (a: number) => (b: number) => (c: number) => a + b + c;
17
+ * const add3 = uncurry3(curriedAdd3);
18
+ * add3(1, 2, 3); // 6
19
+ * ```
20
+ */
21
+ export const uncurry3 = (f) => (a, b, c) => f(a)(b)(c);
22
+ /**
23
+ * Converts a curried 4-argument function into a multi-argument function.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const curriedAdd4 = (a: number) => (b: number) => (c: number) => (d: number) => a + b + c + d;
28
+ * const add4 = uncurry4(curriedAdd4);
29
+ * add4(1, 2, 3, 4); // 10
30
+ * ```
31
+ */
32
+ export const uncurry4 = (f) => (a, b, c, d) => f(a)(b)(c)(d);