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,246 @@
1
+ /**
2
+ * A computation invoked without arguments to obtain a Promise.
3
+ * Call the task to run it; calling it again reruns its body. Tasks do not cache
4
+ * results automatically. A captured promise may already be running or settled.
5
+ *
6
+ * @template A
7
+ * @typedef {() => Promise<A>} Task
8
+ */
9
+
10
+ /**
11
+ * Creates a task for a captured value, preserving object references.
12
+ * Functions remain data. Promises and thenables are adopted when the task runs;
13
+ * an already-created promise is not restarted or made lazy.
14
+ *
15
+ * @template A
16
+ * @param {A | PromiseLike<A>} value - The value or promise-like result to capture.
17
+ * @returns {Task<A>} The task for the captured value.
18
+ * @example
19
+ * const hero = of("Link");
20
+ * await hero(); // "Link"
21
+ */
22
+ export const of = (value) => () => Promise.resolve(value);
23
+
24
+
25
+ /**
26
+ * Defers a function call until task execution, repeating it on every run.
27
+ * Adopts returned promises and thenables. Synchronous throws become rejections.
28
+ *
29
+ * @template A
30
+ * @param {() => A | PromiseLike<A>} func - Called without arguments on each execution.
31
+ * @returns {Task<A>} The deferred task.
32
+ * @example
33
+ * let calls = 0;
34
+ * const count = defer(() => ++calls);
35
+ * await count(); // 1
36
+ * await count(); // 2
37
+ */
38
+ export const defer = (func) => async() =>
39
+ func();
40
+
41
+
42
+ /**
43
+ * Creates a delay task. Each execution schedules a new timer.
44
+ * Validates the delay immediately, before any task is run.
45
+ *
46
+ * @param {number} ms - An integer delay in milliseconds, from 0 through 2147483647.
47
+ * @returns {Task<undefined>} A task resolving to undefined after the delay.
48
+ * @throws {RangeError} If ms is not an integer in the supported range.
49
+ * @example
50
+ * await sleep(0)(); // undefined
51
+ */
52
+ export const sleep = (ms) => {
53
+ if (!Number.isInteger(ms) || ms < 0 || ms > 2147483647) {
54
+ throw new RangeError("Milliseconds must be an integer between 0 and 2147483647");
55
+ }
56
+
57
+ return () =>
58
+ new Promise((resolve) => setTimeout(resolve, ms));
59
+ }
60
+
61
+
62
+ /**
63
+ * Transforms a task's resolved value, awaiting a returned promise or thenable.
64
+ * Repeats the source and transform on each execution; a rejection skips transform.
65
+ * A callback throw rejects the promise; a source throw before returning a promise
66
+ * throws directly.
67
+ *
68
+ * @template A, B
69
+ * @param {(value: A) => B | PromiseLike<B>} transform - Called once with the fulfilled value.
70
+ * @returns {(task: Task<A>) => Task<B>} A function mapping a task.
71
+ * @example
72
+ * const heal = map(async health => health + 1)(of(3));
73
+ * await heal(); // 4
74
+ */
75
+ export const map = (transform) => (task) => () =>
76
+ task().then(transform);
77
+
78
+
79
+ /**
80
+ * Chains a resolved value into another task and runs it.
81
+ * The callback may return the next task or a promise of it. Repeats the chain on
82
+ * each execution; a source rejection skips the callback. Callback and next-task
83
+ * failures reject the promise; a source throw before returning a promise throws directly.
84
+ *
85
+ * @template A, B
86
+ * @param {(value: A) => Task<B> | PromiseLike<Task<B>>} transform -
87
+ * Supplies the next task for the resolved value.
88
+ * @returns {(task: Task<A>) => Task<B>} A function chaining tasks.
89
+ * @example
90
+ * const heal = flatMap(health => of(health + 1))(of(3));
91
+ * await heal(); // 4
92
+ */
93
+ export const flatMap = (transform) => (task) => () =>
94
+ task().then(transform).then((t) => t());
95
+
96
+
97
+ /**
98
+ * Removes one nested Task layer by running the outer task, then the resolved task.
99
+ * Runs the inner task only if the outer task fulfills. Rejections propagate.
100
+ * An outer-task throw before returning a promise throws directly; an inner-task
101
+ * throw rejects the returned promise.
102
+ *
103
+ * @template A
104
+ * @param {Task<Task<A>>} task - A task resolving to another task.
105
+ * @returns {Task<A>} The task for the inner result.
106
+ * @example
107
+ * await flatten(of(of("Link")))(); // "Link"
108
+ */
109
+ export const flatten = (task) => () =>
110
+ task().then((t) => t());
111
+
112
+
113
+ /**
114
+ * Runs an effect after fulfillment and preserves the value; skips the effect on rejection.
115
+ * Repeats on each execution. Ignores the effect's return value without awaiting it;
116
+ * use tapAsync for async effects. An effect throw rejects the promise; a source throw
117
+ * before returning a promise throws directly.
118
+ *
119
+ * @template A
120
+ * @param {(value: A) => unknown} effect - Observes the resolved value.
121
+ * @returns {(task: Task<A>) => Task<A>} A function adding a synchronous effect.
122
+ * @example
123
+ * const seen = [];
124
+ * const hero = tap(name => { seen.push(name); })(of("Link"));
125
+ * await hero(); // "Link"
126
+ * seen; // ["Link"]
127
+ */
128
+ export const tap = (effect) => (task) => () =>
129
+ task().then(result => {
130
+ effect(result);
131
+ return result;
132
+ });
133
+
134
+
135
+ /**
136
+ * Awaits an effect after fulfillment and preserves the value; skips it on failure.
137
+ * Repeats on each execution. Source or effect throws and rejections reject the task.
138
+ *
139
+ * @template A
140
+ * @param {(value: A) => unknown} effect - Observes the resolved value; may return a promise.
141
+ * @returns {(task: Task<A>) => Task<A>} A function adding an awaited effect.
142
+ * @example
143
+ * const seen = [];
144
+ * const hero = tapAsync(async name => {
145
+ * await Promise.resolve();
146
+ * seen.push(name);
147
+ * })(of("Link"));
148
+ * await hero(); // "Link"
149
+ * seen; // ["Link"]
150
+ */
151
+ export const tapAsync = (effect) => (task) => async () => {
152
+ const result = await task();
153
+
154
+ await effect(result);
155
+
156
+ return result;
157
+ }
158
+
159
+
160
+ /**
161
+ * Runs tasks concurrently and collects their results in input order.
162
+ * Each execution calls the supplied task functions again and produces a new array.
163
+ * Empty input resolves to an empty array. Rejections do not cancel started tasks.
164
+ * A synchronous task throw rejects the combined task and prevents later tasks
165
+ * from starting.
166
+ * Array holes become undefined entries in the output.
167
+ *
168
+ * @template A
169
+ * @param {ReadonlyArray<Task<A>>} tasks - Tasks invoked without arguments in array order.
170
+ * @returns {Task<(A | undefined)[]>} The task collecting all values.
171
+ * @example
172
+ * await all([of("Link"), of("Zelda")])(); // ["Link", "Zelda"]
173
+ */
174
+ export const all = (tasks) => async () => {
175
+ const promises = tasks.map((task) => task());
176
+ return Promise.all(promises);
177
+ }
178
+
179
+
180
+ /**
181
+ * Creates and runs tasks concurrently, collecting values in input order.
182
+ * Repeats transforms and tasks on each execution. Empty input resolves to [].
183
+ * Rejections do not cancel running tasks. A synchronous transform or task throw rejects
184
+ * the combined task and skips later inputs. Array holes skip transform and produce undefined.
185
+ *
186
+ * @template A, B
187
+ * @param {(value: A) => Task<B>} transform - Creates a task from each value, without an index.
188
+ * @returns {(values: ReadonlyArray<A>) => Task<(B | undefined)[]>}
189
+ * A function collecting transformed tasks.
190
+ * @example
191
+ * const healAll = traverse(health => of(health + 1))([2, 3]);
192
+ * await healAll(); // [3, 4]
193
+ */
194
+ export const traverse = (transform) => (values) => async () => {
195
+ const promises = values.map((value) => {
196
+ const task = transform(value);
197
+ return task();
198
+ });
199
+
200
+ return Promise.all(promises);
201
+ }
202
+
203
+
204
+ /**
205
+ * Awaits cleanup after the source succeeds, rejects, or throws synchronously.
206
+ * Repeats the source and cleanup on each execution. Successful cleanup preserves
207
+ * the original outcome. Cleanup failure after source success rejects with that
208
+ * failure. If both fail, rejects with an AggregateError whose errors array holds
209
+ * [sourceError, cleanupError].
210
+ *
211
+ * @param {() => unknown} cleanup - Cleanup to run and await without arguments.
212
+ * @returns {<A>(task: Task<A>) => Task<A>} A function attaching cleanup.
213
+ * @example
214
+ * const events = [];
215
+ * const loadHero = defer(() => { events.push("loaded"); return "Link"; });
216
+ * const hero = withCleanup(() => { events.push("cleaned"); })(loadHero);
217
+ * await hero(); // "Link"
218
+ * events; // ["loaded", "cleaned"]
219
+ */
220
+ export const withCleanup = (cleanup) => (task) => async () => {
221
+ let result;
222
+
223
+ let taskError;
224
+ let taskFailed = false;
225
+
226
+ try {
227
+ result = await task();
228
+ } catch (error) {
229
+ taskError = error;
230
+ taskFailed = true;
231
+ }
232
+
233
+ try {
234
+ await cleanup();
235
+ } catch (error) {
236
+ throw taskFailed
237
+ ? new AggregateError([taskError, error])
238
+ : error;
239
+ }
240
+
241
+ if (taskFailed) {
242
+ throw taskError;
243
+ }
244
+
245
+ return result;
246
+ }