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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kyrylo Tykhanskyi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,139 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <img src="public/logo-transparent.png" alt="OnlyFuncs banner" width="700">
3
+ </p>
2
4
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
5
+ # OnlyFuncs
6
+
7
+ Lightweight and hopefully ready to use library for functional programming in JavaScript.
8
+ It was written out of curiosity and because someone got really bored with OOP.
9
+
10
+
11
+ ## Description
12
+
13
+ Provides a wide set of functions and types that are quite regular for functional programming. For example map,
14
+ pipe, compose, result, option, traverse etc. So if you want to give FP a shot, save yourself some time and try
15
+ the lib. It doesn't bite. Mostly.
16
+
17
+ Also, it does its best to be readable and supports asynchronous operations.
18
+
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install onlyfuncs
24
+ ```
25
+
26
+
27
+ ## Quick Start
28
+
29
+ Import the functions you need from the library module:
30
+
31
+ ```js
32
+ import { pipe } from "onlyfuncs/function";
33
+ import * as Option from "onlyfuncs/option";
34
+ import * as Result from "onlyfuncs/result";
35
+ ```
36
+
37
+ Or directly from the root:
38
+
39
+ ```js
40
+ import { Option, Result } from "onlyfuncs";
41
+ ```
42
+
43
+ Use them in chains:
44
+
45
+ ```js
46
+ const racer = (name) => pipe(
47
+ Option.fromNullable(name),
48
+ Option.map((name) => name.trim()),
49
+ Option.filter((name) => name.length > 0),
50
+ Option.getOrElse(() => "Luigi")
51
+ );
52
+
53
+ console.log(racer(" Mario ")) // Mario
54
+ console.log(racer(" ")) // Luigi
55
+ console.log(racer()) // Luigi
56
+ console.log(racer("Bowser")) // Bowser
57
+ ```
58
+
59
+ Reach out to Result if you want to check why operation failed:
60
+
61
+ ```js
62
+ const racer = (value) => pipe(
63
+ Result.fromThrowable(() => JSON.parse(value)),
64
+ Result.mapError((_) => "invalid JSON"),
65
+ Result.map((user) => user?.name),
66
+ Result.flatMap((name) => typeof name === "string" && name.trim().length > 0
67
+ ? Result.ok(name.trim())
68
+ : Result.err("name is missing")),
69
+ Result.match({
70
+ onOk: (name) => `Select: ${name}`,
71
+ onErr: (err) => `Could not choose a racer: ${err}`
72
+ })
73
+ );
74
+
75
+
76
+ console.log(racer('{"name": "Mario"}')); // Mario
77
+ console.log(racer('{"na": "Luigi"}')); // Name is missing
78
+ console.log(racer('{')); // Invalid JSON
79
+ console.log(racer('{"name": "Bowser", "power": "fireball"}')); // Bowser
80
+ ```
81
+
82
+ Reach out to TaskResult for running async operations that may fail:
83
+
84
+ ```js
85
+ import { pipe } from "onlyfuncs/function";
86
+ import * as TaskResult from "onlyfuncs/task-result";
87
+
88
+ const lookupItem = async (name) => {
89
+ const response = await fetch(
90
+ `https://dummyjson.com/products/search?q=${encodeURIComponent(name)}`,
91
+ );
92
+
93
+ if (!response.ok) {
94
+ throw new Error(`Request failed: ${response.status}`);
95
+ }
96
+
97
+ return await response.json();
98
+ };
99
+
100
+ const item = (name) => pipe(
101
+ TaskResult.fromThrowable(() => lookupItem(name)),
102
+ TaskResult.mapError((err) => err.message),
103
+ TaskResult.map((data) => data.products[0]),
104
+ TaskResult.filterOrElse({
105
+ predicate: (item) => item !== undefined,
106
+ onFalse: () => `${name} was not found`,
107
+ }),
108
+ TaskResult.map((item) => item.title),
109
+ TaskResult.match({
110
+ onOk: (title) => `Gotcha ${title}`,
111
+ onErr: (err) => `Could not get item: ${err}`,
112
+ }),
113
+ );
114
+
115
+ console.log(await item("cup")());
116
+ console.log(await item("apple")());
117
+ console.log(await item("moonstone")());
118
+ ```
119
+
120
+
121
+ ## Modules
122
+
123
+ | Module | Import | Purpose |
124
+ | --- | --- | --- |
125
+ | Function | `onlyfuncs/function` | Compose and transform functions with `pipe`, `flow`, and `compose`|
126
+ | Option | `onlyfuncs/option` | Represent optional values and handle missing data|
127
+ | Result | `onlyfuncs/result` | Represent success or failure and recover from errors|
128
+ | Predicate | `onlyfuncs/predicate` | Combine and adapt boolean checks |
129
+ | ReadonlyArray | `onlyfuncs/readonly-array` | Find, group, and transform array values |
130
+ | Task | `onlyfuncs/task` | Define lazy asynchronous computations |
131
+ | TaskResult | `onlyfuncs/task-result` | Combine asynchronous computations with success and failure handling |
132
+ | Semigroup | `onlyfuncs/semigroup` | Combine values, such as strings, arrays, and records |
133
+ | Monoid | `onlyfuncs/monoid` | Combine values with an identity |
134
+ | Validation | `onlyfuncs/validation` | Accumulate errors from independent checks |
135
+
136
+
137
+ ## License
138
+
139
+ MIT © 2026 Kyrylo Tykhanskyi. See [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
2
  "name": "onlyfuncs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "description": "Lightweight library for functional programming",
4
+ "version": "0.1.0",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": "https://github.com/devkyt/onlyfuncs",
8
+ "scripts": {
9
+ "test": "node --test"
10
+ },
11
+ "files": [
12
+ "src/index.js",
13
+ "src/*/index.js"
14
+ ],
15
+ "exports": {
16
+ ".": "./src/index.js",
17
+ "./function": "./src/function/index.js",
18
+ "./option": "./src/option/index.js",
19
+ "./result": "./src/result/index.js",
20
+ "./predicate": "./src/predicate/index.js",
21
+ "./readonly-array": "./src/readonly-array/index.js",
22
+ "./task": "./src/task/index.js",
23
+ "./task-result": "./src/task-result/index.js",
24
+ "./semigroup": "./src/semigroup/index.js",
25
+ "./monoid": "./src/monoid/index.js",
26
+ "./validation": "./src/validation/index.js"
27
+ },
28
+ "publishConfig": {
29
+ "registry": "https://registry.npmjs.org/"
30
+ }
31
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Returns the input unchanged, preserving object references.
3
+ *
4
+ * @template A
5
+ * @param {A} value - The value to return.
6
+ * @returns {A} The original value.
7
+ * @example
8
+ * identity("Link"); // "Link"
9
+ */
10
+ export const identity = (value) => value;
11
+
12
+
13
+ /**
14
+ * Creates a function that returns the same captured value on every call.
15
+ * Object references are preserved.
16
+ *
17
+ * @template A
18
+ * @param {A} value - The value to capture.
19
+ * @returns {() => A} The constant function.
20
+ * @example
21
+ * const hero = constant("Link");
22
+ * hero(); // "Link"
23
+ */
24
+ export const constant = (value) => () => value;
25
+
26
+
27
+ /**
28
+ * Immediately passes a value through steps from left to right.
29
+ * Each step receives only the previous result. With no steps, returns the input.
30
+ * Promises are passed along without awaiting. A throw stops the remaining steps.
31
+ *
32
+ * @param {any} value - The input passed to the first step.
33
+ * @param {...((value: any) => any)} functions - Steps to apply in order.
34
+ * @returns {any} The final value, or the input if no steps are given.
35
+ * @throws {TypeError} When a non-function step is reached.
36
+ * @example
37
+ * pipe(3, n => n + 1, n => n * 2); // 8
38
+ */
39
+ export const pipe = (value, ...functions) => {
40
+ const apply = (value, fn) => {
41
+ if (typeof fn !=='function') {
42
+ throw new TypeError('pipe: expected all arguments to be functions');
43
+ }
44
+
45
+ return fn(value)
46
+ }
47
+
48
+ return functions.reduce((acc, fn) => apply(acc, fn), value);
49
+ }
50
+
51
+ /**
52
+ * Creates a function that passes its input through steps from left to right.
53
+ * Each step receives only the previous result. With no steps, returns the input.
54
+ * Promises are passed along without awaiting. A throw stops the remaining steps.
55
+ *
56
+ * @param {...((value: any) => any)} functions - Steps to apply in order.
57
+ * @returns {(value: any) => any} The composed function.
58
+ * @throws {TypeError} When the returned function reaches a non-function step.
59
+ * @example
60
+ * const calculate = flow(n => n + 1, n => n * 2);
61
+ * calculate(3); // 8
62
+ */
63
+ export const flow = (...functions) => (value) => pipe(value, ...functions);
64
+
65
+
66
+ /**
67
+ * Creates a function that passes its input through steps from right to left.
68
+ * Each step receives only the previous result. With no steps, returns the input.
69
+ * Promises are passed along without awaiting. A throw stops the remaining steps.
70
+ *
71
+ * @param {...((value: any) => any)} functions - Steps to apply in reverse order.
72
+ * @returns {(value: any) => any} The composed function.
73
+ * @throws {TypeError} When the returned function reaches a non-function step.
74
+ * @example
75
+ * const calculate = compose(n => n * 2, n => n + 1);
76
+ * calculate(3); // 8
77
+ */
78
+ export const compose = (...functions) => flow(...functions.reverse());
79
+
80
+
81
+ /**
82
+ * Converts a two-argument function into two successive one-argument calls.
83
+ * Calls fn after receiving the second argument, preserving argument order.
84
+ * Exceptions from fn propagate.
85
+ *
86
+ * @template A, B, R
87
+ * @param {(first: A, second: B) => R} fn - The two-argument function.
88
+ * @returns {(first: A) => (second: B) => R} The curried function.
89
+ * @example
90
+ * const subtract = curry2((a, b) => a - b);
91
+ * subtract(10)(3); // 7
92
+ */
93
+ export const curry2 = (fn) => (x) => (y) => fn(x, y);
94
+
95
+
96
+ /**
97
+ * Converts a three-argument function into three successive one-argument calls.
98
+ * Calls fn after receiving the third argument, preserving argument order.
99
+ * Exceptions from fn propagate.
100
+ *
101
+ * @template A, B, C, R
102
+ * @param {(first: A, second: B, third: C) => R} fn - The three-argument function.
103
+ * @returns {(first: A) => (second: B) => (third: C) => R} The curried function.
104
+ * @example
105
+ * const cast = curry3((caster, spell, target) => `${caster}: ${spell} on ${target}`);
106
+ * cast("Rauru")("Blizzard")("Goblin"); // "Rauru: Blizzard on Goblin"
107
+ */
108
+ export const curry3 = (fn) => (x) => (y) => (z) => fn(x, y, z);
109
+
package/src/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export * as Function from "./function/index.js";
2
+ export * as Option from "./option/index.js";
3
+ export * as Result from "./result/index.js";
4
+ export * as Predicate from "./predicate/index.js";
5
+ export * as ReadonlyArray from "./readonly-array/index.js";
6
+ export * as Task from "./task/index.js";
7
+ export * as TaskResult from "./task-result/index.js";
8
+ export * as Semigroup from "./semigroup/index.js";
9
+ export * as Monoid from "./monoid/index.js";
10
+ export * as Validation from "./validation/index.js";
@@ -0,0 +1,121 @@
1
+ import * as Semigroup from "../semigroup/index.js"
2
+
3
+ /**
4
+ * An associative combination operation with an identity value.
5
+ * Combining a value with the identity on either side should preserve that value.
6
+ * Custom combiners should preserve inputs, and empty() should create a fresh identity
7
+ * when it is mutable. JavaScript number rounding can limit numeric associativity.
8
+ *
9
+ * @template A
10
+ * @typedef {Object} Monoid
11
+ * @property {(left: A, right: A) => A} combine - Combines the left and right values in that order.
12
+ * @property {() => A} empty - Supplies the identity for the combination operation.
13
+ */
14
+
15
+ /**
16
+ * Combines numbers by addition, with zero as the identity.
17
+ * Uses JavaScript number arithmetic.
18
+ *
19
+ * @type {Monoid<number>}
20
+ * @example
21
+ * sum.combine(sum.empty(), 3); // 3
22
+ */
23
+ export const sum = {
24
+ combine: Semigroup.sum.combine,
25
+ empty: () => 0,
26
+ };
27
+
28
+
29
+ /**
30
+ * Combines numbers by multiplication, with one as the identity.
31
+ * Uses JavaScript number arithmetic.
32
+ *
33
+ * @type {Monoid<number>}
34
+ * @example
35
+ * product.combine(product.empty(), 3); // 3
36
+ */
37
+ export const product = {
38
+ combine: Semigroup.product.combine,
39
+ empty: () => 1,
40
+ };
41
+
42
+
43
+ /**
44
+ * Concatenates strings in left-to-right order, with the empty string as the identity.
45
+ *
46
+ * @type {Monoid<string>}
47
+ * @example
48
+ * string.combine(string.empty(), "Link"); // "Link"
49
+ */
50
+ export const string = {
51
+ combine: Semigroup.string.combine,
52
+ empty: () => "",
53
+ };
54
+
55
+
56
+ /**
57
+ * Concatenates arrays without changing them; empty() creates a new empty array.
58
+ * Each combination creates a new array, preserving the elements' references.
59
+ *
60
+ * @type {{
61
+ * combine: <A, B>(left: ReadonlyArray<A>, right: ReadonlyArray<B>) => (A | B)[],
62
+ * empty: <A>() => A[]
63
+ * }}
64
+ * @example
65
+ * array.combine(array.empty(), ["Link"]); // ["Link"]
66
+ * array.empty() === array.empty(); // false
67
+ */
68
+ export const array = {
69
+ combine: Semigroup.array.combine,
70
+ empty: () => [],
71
+ };
72
+
73
+
74
+ /**
75
+ * Creates a monoid that combines matching fields into a new object.
76
+ * Captures the schema's own enumerable string keys and monoid references at creation.
77
+ * Ignores extra input fields. Each empty() call creates a new object and calls each
78
+ * field's empty() once; field monoids control freshness of their values.
79
+ * Field operations run in schema order; exceptions propagate.
80
+ *
81
+ * @template {Record<string, any>} A
82
+ * @param {{[K in keyof A]: Monoid<A[K]>}} fields - A monoid for each field to combine.
83
+ * @returns {Monoid<A>} The record monoid.
84
+ * @example
85
+ * const stats = struct({ health: sum, inventory: array });
86
+ * stats.empty(); // { health: 0, inventory: [] }
87
+ * stats.combine(stats.empty(), { health: 3, inventory: ["Bow"] });
88
+ * // { health: 3, inventory: ["Bow"] }
89
+ */
90
+ export const struct = (fields) => {
91
+ const entries = Object.entries(fields);
92
+
93
+ return {
94
+ combine: Semigroup.struct(fields).combine,
95
+ empty: () => Object.fromEntries(
96
+ entries.map(([key, monoid]) => [
97
+ key,
98
+ monoid.empty()
99
+ ])
100
+ ),
101
+ };
102
+ };
103
+
104
+
105
+ /**
106
+ * Combines a finite iterable left to right, starting from the monoid's identity.
107
+ * Calls empty() once when values are supplied, including for empty input.
108
+ * Iteration, empty(), and combiner exceptions propagate.
109
+ *
110
+ * @template A
111
+ * @param {Monoid<A>} monoid - Supplies the identity and combines the accumulator with each element.
112
+ * @returns {(values: Iterable<A>) => A} A function combining a finite iterable.
113
+ * @example
114
+ * combineAll(sum)([2, 3, 4]); // 9
115
+ * combineAll(sum)([]); // 0
116
+ */
117
+ export const combineAll = (monoid) => (values) =>
118
+ Semigroup.combineMany({ semigroup: monoid, initial: monoid.empty() })(values);
119
+
120
+
121
+