onlyfuncs 0.0.0-stage → 0.1.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.
@@ -0,0 +1,321 @@
1
+ import * as Result from "../result/index.js";
2
+
3
+ /**
4
+ * A present value. The payload may itself be null or undefined.
5
+ *
6
+ * @template A
7
+ * @typedef {Object} Some
8
+ * @property {"some"} type - Identifies a present value.
9
+ * @property {A} value - The wrapped value.
10
+ */
11
+
12
+ /**
13
+ * An absent value.
14
+ *
15
+ * @typedef {Object} None
16
+ * @property {"none"} type - Identifies absence.
17
+ */
18
+
19
+ /**
20
+ * A value that is either present or absent.
21
+ *
22
+ * @template A
23
+ * @typedef {Some<A> | None} Option
24
+ */
25
+
26
+ /**
27
+ * Wraps a value in Some, preserving object references.
28
+ * Unlike fromNullable, null and undefined are treated as present values.
29
+ *
30
+ * @template A
31
+ * @param {A} value - The value to wrap.
32
+ * @returns {Some<A>} A new Some.
33
+ * @example
34
+ * some("Link"); // { type: "some", value: "Link" }
35
+ */
36
+ export const some = (value) => ({ type: "some", value });
37
+
38
+
39
+ /**
40
+ * The shared value representing absence. Treat this object as immutable.
41
+ *
42
+ * @type {None}
43
+ * @example
44
+ * none; // { type: "none" }
45
+ */
46
+ export const none = { type: "none" };
47
+
48
+
49
+ /**
50
+ * Checks whether an Option is Some, regardless of its payload's truthiness.
51
+ *
52
+ * @template A
53
+ * @param {Option<A>} option - The Option to inspect, not an arbitrary value.
54
+ * @returns {option is Some<A>} Whether the option has the Some tag.
55
+ * @example
56
+ * isSome(some(undefined)); // true
57
+ */
58
+ export const isSome = (option) => option.type === "some";
59
+
60
+
61
+ /**
62
+ * Checks whether an Option is None.
63
+ *
64
+ * @template A
65
+ * @param {Option<A>} option - The Option to inspect, not an arbitrary value.
66
+ * @returns {option is None} Whether the option has the None tag.
67
+ * @example
68
+ * isNone(none); // true
69
+ */
70
+ export const isNone = (option) => option.type === "none";
71
+
72
+
73
+ /**
74
+ * Converts null and undefined to None; wraps every other value in Some.
75
+ * Values such as 0, false, and an empty string remain present.
76
+ *
77
+ * @template A
78
+ * @param {A} value - The possibly missing value.
79
+ * @returns {Option<NonNullable<A>>} Some for a present value, otherwise None.
80
+ * @example
81
+ * fromNullable(0); // { type: "some", value: 0 }
82
+ * fromNullable(null); // { type: "none" }
83
+ */
84
+ export const fromNullable = (value) =>
85
+ value === null || value === undefined ? none : some(value);
86
+
87
+
88
+ /**
89
+ * Returns Some when the predicate passes, otherwise None.
90
+ * Calls the predicate with the input, including undefined. Exceptions propagate.
91
+ *
92
+ * @template A
93
+ * @param {(value: A) => boolean} predicate - Called once with the supplied value.
94
+ * @returns {(value: A) => Option<A>} A function testing and wrapping a value.
95
+ * @example
96
+ * fromPredicate(health => health > 0)(3); // { type: "some", value: 3 }
97
+ * fromPredicate(value => value === undefined)(undefined); // { type: "some", value: undefined }
98
+ */
99
+ export const fromPredicate = (predicate) => (value) =>
100
+ predicate(value) ? some(value) : none;
101
+
102
+
103
+ /**
104
+ * Converts Ok to Some and Err to None, discarding the error.
105
+ * Successful null and undefined values remain present.
106
+ *
107
+ * @template A, E
108
+ * @param {import("../result/index.js").Result<A, E>} result - The Result to convert.
109
+ * @returns {Option<A>} The converted Option.
110
+ * @example
111
+ * fromResult(Result.ok("Link")); // { type: "some", value: "Link" }
112
+ */
113
+ export const fromResult = (result) =>
114
+ Result.isOk(result) ? some(result.value) : none;
115
+
116
+
117
+ /**
118
+ * Transforms a Some value; skips None. Wraps the output without flattening
119
+ * or awaiting it. Callback exceptions propagate.
120
+ *
121
+ * @template A, B
122
+ * @param {(value: A) => B} transform - Receives the entire Some payload.
123
+ * @returns {(option: Option<A>) => Option<B>} A function mapping Some values.
124
+ * @example
125
+ * map(name => name.toUpperCase())(some("Link")); // { type: "some", value: "LINK" }
126
+ * map(name => some(name.toUpperCase()))(some("Link"));
127
+ * // { type: "some", value: { type: "some", value: "LINK" } }
128
+ */
129
+ export const map = (transform) => (option) =>
130
+ isSome(option) ? some(transform(option.value)) : none;
131
+
132
+
133
+ /**
134
+ * Chains a Some value into another Option; skips None.
135
+ * Returns the callback's Option directly. Callback exceptions propagate.
136
+ *
137
+ * @template A, B
138
+ * @param {(value: A) => Option<B>} transform - Receives the payload and returns an Option.
139
+ * @returns {(option: Option<A>) => Option<B>} A function chaining Options.
140
+ * @example
141
+ * const petName = flatMap(hero => fromNullable(hero.pet));
142
+ * petName(some({ pet: "Epona" })); // { type: "some", value: "Epona" }
143
+ * petName(some({})); // { type: "none" }
144
+ */
145
+ export const flatMap = (transform) => (option) =>
146
+ isSome(option) ? transform(option.value) : none;
147
+
148
+
149
+ /**
150
+ * Removes one nested Option layer. An outer None stays absent.
151
+ *
152
+ * @template A
153
+ * @param {Option<Option<A>>} option - An Option whose Some payload is another Option.
154
+ * @returns {Option<A>} The inner Option, or the shared None for an outer None.
155
+ * @example
156
+ * flatten(some(some("Link"))); // { type: "some", value: "Link" }
157
+ */
158
+ export const flatten = (option) =>
159
+ isSome(option) ? option.value : none;
160
+
161
+
162
+ /**
163
+ * Keeps the original Some when its payload passes the predicate.
164
+ * Returns None on failure and skips the predicate for None. Exceptions propagate.
165
+ *
166
+ * @template A
167
+ * @param {(value: A) => boolean} predicate - Called once with a Some payload.
168
+ * @returns {(option: Option<A>) => Option<A>} A function filtering Some values.
169
+ * @example
170
+ * filter(health => health > 0)(some(0)); // { type: "none" }
171
+ */
172
+ export const filter = (predicate) => (option) =>
173
+ isSome(option) && predicate(option.value) ? option : none;
174
+
175
+
176
+ /**
177
+ * Calls a fallback only for None; preserves an existing Some unchanged.
178
+ * Callback exceptions propagate.
179
+ *
180
+ * @template B
181
+ * @param {() => Option<B>} fallback - Called without arguments to supply an Option.
182
+ * @returns {<A>(option: Option<A>) => Option<A | B>} A function supplying a fallback Option.
183
+ * @example
184
+ * orElse(() => some("Link"))(none); // { type: "some", value: "Link" }
185
+ */
186
+ export const orElse = (fallback) => (option) =>
187
+ isSome(option) ? option : fallback();
188
+
189
+
190
+ /**
191
+ * Runs an effect for Some and returns the original Option.
192
+ * Skips the effect for None. The effect's result is ignored, promises are not
193
+ * awaited, and synchronous exceptions propagate.
194
+ *
195
+ * @template A
196
+ * @param {(value: A) => unknown} effect - Observes the Some value.
197
+ * @returns {(option: Option<A>) => Option<A>} A function observing an Option.
198
+ * @example
199
+ * const seen = [];
200
+ * tap(name => { seen.push(name); })(some("Link"));
201
+ * seen; // ["Link"]
202
+ */
203
+ export const tap = (effect) => (option) => {
204
+ if (isSome(option)) {
205
+ effect(option.value);
206
+ }
207
+
208
+ return option;
209
+ }
210
+
211
+
212
+ /**
213
+ * Unwraps Some or calls fallback for None. Callback exceptions propagate.
214
+ *
215
+ * @template B
216
+ * @param {() => B} fallback - Called without arguments to supply the fallback value.
217
+ * @returns {<A>(option: Option<A>) => A | B} A function unwrapping an Option.
218
+ * @example
219
+ * getOrElse(() => "Link")(none); // "Link"
220
+ */
221
+ export const getOrElse = (fallback) => (option) =>
222
+ isSome(option) ? option.value : fallback();
223
+
224
+
225
+ /**
226
+ * Calls the matching handler and returns its output directly.
227
+ * Callback exceptions propagate.
228
+ *
229
+ * @template A, B, C
230
+ * @param {Object} handlers - Callbacks for the two possible branches.
231
+ * @param {(value: A) => B} handlers.onSome - Called with the Some payload.
232
+ * @param {() => C} handlers.onNone - Called without arguments for None.
233
+ * @returns {(option: Option<A>) => B | C} A function handling either branch.
234
+ * @example
235
+ * const greet = match({ onSome: name => `Hello, ${name}`, onNone: () => "Nobody here" });
236
+ * greet(some("Link")); // "Hello, Link"
237
+ */
238
+ export const match = ({ onSome, onNone }) => (option) =>
239
+ isSome(option) ? onSome(option.value) : onNone();
240
+
241
+
242
+ /**
243
+ * Converts Some to a one-element array and None to an empty array.
244
+ * Creates a new array while preserving the payload reference.
245
+ *
246
+ * @template A
247
+ * @param {Option<A>} option - The Option to convert.
248
+ * @returns {A[]} A new array containing zero or one value.
249
+ * @example
250
+ * toArray(some("Link")); // ["Link"]
251
+ */
252
+ export const toArray = (option) =>
253
+ isSome(option) ? [option.value] : [];
254
+
255
+
256
+ /**
257
+ * Combines two Some payloads or returns None if either input is absent.
258
+ * The combiner runs only when both inputs are Some. Exceptions propagate.
259
+ *
260
+ * @template A, B, C
261
+ * @param {(left: A, right: B) => C} combine - Combines the two Some values.
262
+ * @returns {(left: Option<A>, right: Option<B>) => Option<C>} A function combining two Options.
263
+ * @example
264
+ * zipWith((a, b) => a + b)(some(2), some(3)); // { type: "some", value: 5 }
265
+ */
266
+ export const zipWith = (combine) => (left, right) =>
267
+ isSome(left) && isSome(right) ? some(combine(left.value, right.value)) : none;
268
+
269
+
270
+ /**
271
+ * Collects Some payloads in iteration order, stopping at the first None.
272
+ * Empty input returns Some containing an empty array.
273
+ *
274
+ * @template A
275
+ * @param {Iterable<Option<A>>} options - A finite iterable of Options to collect.
276
+ * @returns {Option<A[]>} Some containing a new array, or the shared None.
277
+ * @example
278
+ * all([some("Link"), some("Zelda")]); // { type: "some", value: ["Link", "Zelda"] }
279
+ * all([some("Link"), none]); // { type: "none" }
280
+ */
281
+ export const all = (options) => {
282
+ const outputs = []
283
+
284
+ for (const option of options) {
285
+ if (isNone(option)) {
286
+ return none;
287
+ }
288
+
289
+ outputs.push(option.value);
290
+ }
291
+
292
+ return some(outputs);
293
+ }
294
+
295
+
296
+ /**
297
+ * Transforms values in iteration order and collects their Some payloads.
298
+ * Stops at the first None without transforming later inputs. Exceptions propagate.
299
+ * Empty input returns Some containing an empty array.
300
+ *
301
+ * @template A, B
302
+ * @param {(value: A) => Option<B>} transform - Called once per visited value, with no index.
303
+ * @returns {(values: Iterable<A>) => Option<B[]>} A function collecting transformed values.
304
+ * @example
305
+ * traverse(name => fromNullable(name))(["Link", null]); // { type: "none" }
306
+ */
307
+ export const traverse = (transform) => (values) => {
308
+ const outputs = []
309
+
310
+ for (const value of values) {
311
+ const option = transform(value);
312
+
313
+ if (isNone(option)) {
314
+ return none;
315
+ }
316
+
317
+ outputs.push(option.value);
318
+ }
319
+
320
+ return some(outputs);
321
+ }
@@ -0,0 +1,72 @@
1
+ import {none, some} from "../option/index.js";
2
+
3
+ /**
4
+ * A synchronous check of a single value. Predicate helpers do not await promises.
5
+ *
6
+ * @template A
7
+ * @callback Predicate
8
+ * @param {A} value - The value to check.
9
+ * @returns {boolean} Whether the value passes the check.
10
+ */
11
+
12
+ /**
13
+ * Negates a predicate's result. Calls it with the input, including undefined.
14
+ * Callback exceptions propagate.
15
+ *
16
+ * @template A
17
+ * @param {Predicate<A>} predicate - Called once with the original input value.
18
+ * @returns {Predicate<A>} The negated predicate.
19
+ * @example
20
+ * const defeated = not(health => health > 0);
21
+ * defeated(0); // true
22
+ */
23
+ export const not = (predicate) => (value) => !predicate(value);
24
+
25
+
26
+ /**
27
+ * Requires all predicates to pass, checking left to right on each invocation.
28
+ * Stops at the first falsy result. With no predicates, always returns true.
29
+ * Callback exceptions propagate and prevent later checks from running.
30
+ *
31
+ * @template A
32
+ * @param {...Predicate<A>} predicates - Synchronous checks receiving the same input value.
33
+ * @returns {Predicate<A>} The combined predicate.
34
+ * @example
35
+ * const canAttack = and(hero => hero.health > 0, hero => hero.stamina > 0);
36
+ * canAttack({ health: 3, stamina: 0 }); // false
37
+ */
38
+ export const and = (...predicates) => (value) =>
39
+ predicates.every((predicate) => predicate(value));
40
+
41
+
42
+ /**
43
+ * Requires at least one predicate to pass, checking left to right on each invocation.
44
+ * Stops at the first truthy result. With no predicates, always returns false.
45
+ * Callback exceptions propagate and prevent later checks from running.
46
+ *
47
+ * @template A
48
+ * @param {...Predicate<A>} predicates - Synchronous checks receiving the same input value.
49
+ * @returns {Predicate<A>} The combined predicate.
50
+ * @example
51
+ * const canHeal = or(hero => hero.potions > 0, hero => hero.knowsCure);
52
+ * canHeal({ potions: 0, knowsCure: true }); // true
53
+ */
54
+ export const or = (...predicates) => (value) =>
55
+ predicates.some((predicate) => predicate(value));
56
+
57
+
58
+ /**
59
+ * Adapts a predicate to check a selected value.
60
+ * Each call runs selector, then passes its result to the predicate.
61
+ * Callback exceptions propagate.
62
+ *
63
+ * @template A, B
64
+ * @param {(value: A) => B} selector - Converts the original input into the predicate's input.
65
+ * @returns {(predicate: Predicate<B>) => Predicate<A>}
66
+ * A function adapting a predicate to the original input.
67
+ * @example
68
+ * const canCast = contramap(spell => spell.cost)(cost => cost <= 20);
69
+ * canCast({ name: "Blizzard", cost: 10 }); // true
70
+ */
71
+ export const contramap = (selector) => (predicate) => (value) =>
72
+ predicate(selector(value));
@@ -0,0 +1,136 @@
1
+ import * as Option from "../option/index.js";
2
+ import * as Result from "../result/index.js";
3
+
4
+
5
+ /**
6
+ * Transforms a finite iterable and collects Some values, skipping None.
7
+ * Returns a new array in input order. Callback throws stop iteration and propagate.
8
+ *
9
+ * @template A, B
10
+ * @param {(value: A) => import("../option/index.js").Option<B>} transform - Called with each element.
11
+ * @returns {(values: Iterable<A>) => B[]} A function collecting present values.
12
+ * @example
13
+ * mapOption(n => n > 0 ? Option.some(n * 2) : Option.none)([1, -1, 3]); // [2, 6]
14
+ */
15
+ export const mapOption = (transform) => (values) => {
16
+ const outputs = [];
17
+
18
+ for (const value of values) {
19
+ const option = transform(value);
20
+
21
+ if (Option.isSome(option)) {
22
+ outputs.push(option.value);
23
+ }
24
+ }
25
+
26
+ return outputs;
27
+ }
28
+
29
+
30
+ /**
31
+ * Transforms a finite iterable and separates Ok values from Err values.
32
+ * Visits every element, preserving input order in both output arrays.
33
+ * Callback throws stop iteration and propagate.
34
+ *
35
+ * @template A, B, E
36
+ * @param {(value: A) => import("../result/index.js").Result<B, E>} transform -
37
+ * Called with each element.
38
+ * @returns {(values: Iterable<A>) => {values: B[], errors: E[]}}
39
+ * A function separating values and errors.
40
+ * @example
41
+ * mapResult(n => n > 0 ? Result.ok(n) : Result.err("Not positive"))([2, -1, 3]);
42
+ * // { values: [2, 3], errors: ["Not positive"] }
43
+ */
44
+ export const mapResult = (transform) => (values) => {
45
+ const outputs = [];
46
+ const errors = [];
47
+
48
+ for (const value of values) {
49
+ const result = transform(value);
50
+
51
+ Result.isOk(result)
52
+ ? outputs.push(result.value)
53
+ : errors.push(result.error);
54
+ }
55
+
56
+ return { values: outputs, errors };
57
+ }
58
+
59
+
60
+
61
+ /**
62
+ * Returns Some for the first match, or None when no element matches.
63
+ * Stops at the match; a matching undefined is still Some(undefined).
64
+ * Predicate exceptions propagate.
65
+ *
66
+ * @template A
67
+ * @param {(value: A) => boolean} predicate - Called with each element until a match is found.
68
+ * @returns {(values: Iterable<A>) => import("../option/index.js").Option<A>}
69
+ * A function searching for a match.
70
+ * @example
71
+ * findFirst(n => n > 3)([1, 5, 7]); // { type: "some", value: 5 }
72
+ */
73
+ export const findFirst = (predicate) => (values) => {
74
+ for (const value of values) {
75
+ if (predicate(value)) {
76
+ return Option.some(value);
77
+ }
78
+ }
79
+
80
+ return Option.none;
81
+ }
82
+
83
+
84
+ /**
85
+ * Reads an element by index, counting negative indices from the end.
86
+ * Returns None for an out-of-bounds index. An in-bounds hole or undefined value
87
+ * returns Some(undefined). Validates the index before receiving the array.
88
+ *
89
+ * @param {number} index - A safe integer; -1 selects the last element.
90
+ * @returns {<A>(values: ReadonlyArray<A>) => import("../option/index.js").Option<A | undefined>}
91
+ * A function reading an array element.
92
+ * @throws {RangeError} If index is not a safe integer.
93
+ * @example
94
+ * at(-1)(["Link", "Zelda"]); // { type: "some", value: "Zelda" }
95
+ * at(2)(["Link"]); // { type: "none" }
96
+ */
97
+ export const at = (index) => {
98
+ if (!Number.isSafeInteger(index)) {
99
+ throw new RangeError("Index must be a safe integer");
100
+ }
101
+
102
+ return (values) => {
103
+ const position = index < 0 ? values.length + index : index;
104
+
105
+ return position >= 0 && position < values.length
106
+ ? Option.some(values[position])
107
+ : Option.none;
108
+ }
109
+ }
110
+
111
+
112
+ /**
113
+ * Groups a finite iterable into a new Map of new arrays, preserving element references.
114
+ * Keys appear in first-seen order; each group keeps input order. Uses Map key equality,
115
+ * including reference identity for object keys. Callback exceptions propagate.
116
+ *
117
+ * @template A, K
118
+ * @param {(value: A) => K} keyOf - Computes a key from each element.
119
+ * @returns {(values: Iterable<A>) => Map<K, A[]>} A function grouping elements.
120
+ * @example
121
+ * groupBy(name => name.length)(["Link", "Zelda", "Navi"]);
122
+ * // new Map([[4, ["Link", "Navi"]], [5, ["Zelda"]]])
123
+ */
124
+ export const groupBy = (keyOf) => (values) => {
125
+ const outputs = new Map();
126
+
127
+ for (const value of values) {
128
+ const key = keyOf(value)
129
+
130
+ outputs.has(key)
131
+ ? outputs.get(key).push(value)
132
+ : outputs.set(key, [value])
133
+ }
134
+
135
+ return outputs;
136
+ }