slang-ts 0.2.1 → 0.2.3
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/CHANGELOG.md +18 -6
- package/README.md +108 -100
- package/dist/index.d.ts +122 -21
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/skill/SKILL.md +362 -50
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
Everything notable in `slang-ts`, grouped by area. No release diary
|
|
3
|
+
Everything notable in `slang-ts`, grouped by area. No release diary: just what's here.
|
|
4
4
|
|
|
5
5
|
## Results and flow
|
|
6
6
|
|
|
7
|
-
- `Ok(value, message?)` keeps an optional success message on the result
|
|
8
|
-
the user. Results without one have no `message` property.
|
|
7
|
+
- `Ok(value, message?)` keeps an optional success message on the result. Results without one have no `message` property.
|
|
9
8
|
- `SafeTry` unwraps Option and Atom values into one result. A Result never nests inside another
|
|
10
9
|
Result, and plain object properties and array elements stay unchanged.
|
|
10
|
+
- `safeTry` is sync in, sync out: a function returning a plain value gets its `Result`
|
|
11
|
+
directly, and a function returning a promise gets a promise of it. With `{ throw: true }`, a
|
|
12
|
+
sync throw is thrown from the call and an async one rejects.
|
|
13
|
+
- `Result.mapErr(fn)` rewrites an `Err` message and keeps its `.raw` cause; `Result.orFn(fn)`
|
|
14
|
+
recovers from an `Err` with the same return rules as `andThen`. Both return the same `Ok`
|
|
15
|
+
untouched.
|
|
16
|
+
- `andThen` and `orFn` on a `Result` return a plain `Result` for a sync callback; an async
|
|
17
|
+
callback gives `Result | Promise<Result>` to `await`.
|
|
18
|
+
- `combineResults` turns an array of `Result`s into one `Ok` of all values, typed as a tuple,
|
|
19
|
+
or the first `Err` with its `.raw` kept. `combineErrors` lists every error message in order.
|
|
11
20
|
|
|
12
21
|
## Collections and data
|
|
13
22
|
|
|
@@ -33,7 +42,8 @@ Everything notable in `slang-ts`, grouped by area. No release diary — just wha
|
|
|
33
42
|
|
|
34
43
|
- `createStepper`: bounded step history with named steps, undo, and explicit `prev` handling.
|
|
35
44
|
- `println` respects the environment: `setEnvironment("production")` turns it into a no-op, and
|
|
36
|
-
`{ printFn }` routes output through an existing logger.
|
|
45
|
+
`{ printFn }` routes output through an existing logger. Plain objects, arrays, Sets and Maps
|
|
46
|
+
print as JSON, circular references included; errors and dates keep their text form.
|
|
37
47
|
|
|
38
48
|
## Typing and packaging
|
|
39
49
|
|
|
@@ -49,5 +59,7 @@ Everything notable in `slang-ts`, grouped by area. No release diary — just wha
|
|
|
49
59
|
|
|
50
60
|
## Documentation
|
|
51
61
|
|
|
52
|
-
- A shorter README tour
|
|
53
|
-
|
|
62
|
+
- A shorter README tour and per-utility reference pages. Each page explains how the utility
|
|
63
|
+
behaves (when it runs, what it returns, how it fails) and every example compiles and runs
|
|
64
|
+
as written.
|
|
65
|
+
- An agent guide at `skill/SKILL.md`, shipped with the package.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
An opinionated functional programming library for TypeScript.
|
|
7
7
|
|
|
8
8
|
A collection of utilities and mechanisms brought over from languages like Rust and Erlang. Each
|
|
9
|
-
utility is deliberately
|
|
9
|
+
utility is deliberately selected and built from scratch with zero dependencies.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -20,26 +20,30 @@ Well I love functional programming so much and hate classes and inheritance with
|
|
|
20
20
|
|
|
21
21
|
## Quick start
|
|
22
22
|
|
|
23
|
-
So let's say
|
|
24
|
-
|
|
23
|
+
So let's say your function can fail. Instead of throwing, you return a `Result`: `Ok` with the
|
|
24
|
+
value or `Err` with a message, and the caller checks `isOk` before reading either:
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
27
|
import { Ok, Err, type Result, println } from "slang-ts";
|
|
28
28
|
|
|
29
|
-
function
|
|
30
|
-
|
|
29
|
+
function splitBill(total: number, people: number): Result<number, string> {
|
|
30
|
+
if (people === 0) {
|
|
31
|
+
return Err("Add at least one person to split the bill");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Ok(total / people);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
const
|
|
37
|
+
const share = splitBill(120, 4);
|
|
34
38
|
|
|
35
|
-
if (
|
|
36
|
-
println("
|
|
39
|
+
if (share.isOk) {
|
|
40
|
+
println("Each person pays:", share.value); // 30
|
|
37
41
|
} else {
|
|
38
|
-
println("
|
|
42
|
+
println("Could not split the bill:", share.error);
|
|
39
43
|
}
|
|
40
44
|
```
|
|
41
45
|
|
|
42
|
-
`println` is optional
|
|
46
|
+
`println` is optional: it's a small development logger, silent in production.
|
|
43
47
|
|
|
44
48
|
## Imports
|
|
45
49
|
|
|
@@ -49,145 +53,145 @@ Import only what you use:
|
|
|
49
53
|
import { option, Ok, Err, atom, match } from "slang-ts";
|
|
50
54
|
```
|
|
51
55
|
|
|
52
|
-
## Choose by
|
|
56
|
+
## Choose by what you want to solve
|
|
53
57
|
|
|
54
58
|
| If you need… | Use |
|
|
55
59
|
|---|---|
|
|
56
|
-
| A value
|
|
57
|
-
| An operation
|
|
60
|
+
| A value which may be missing | [Option][option] |
|
|
61
|
+
| An operation which can fail | [Result][result] and [SafeTry][safe-try] |
|
|
62
|
+
| Several results checked together | [combineResults and combineErrors][combine] |
|
|
58
63
|
| Pattern matching over results | [Match][match] |
|
|
64
|
+
| Steps which stop at the first failure | [Pipe][pipe] |
|
|
59
65
|
| Timeout, retry, or cancellation | [SuperPromise][super-promise] |
|
|
60
66
|
| Ordered, parallel, or race tasks | [SuperRunner][super-runner] |
|
|
61
67
|
| A shared concurrency limit | [taskGroup][task-group] |
|
|
62
68
|
| Strict array bounds or paging | [list][list] |
|
|
63
|
-
|
|
|
64
|
-
| Set membership and
|
|
69
|
+
| Nested record reads with defaults | [struct][struct] |
|
|
70
|
+
| Set membership and set operations | [sets][sets] |
|
|
65
71
|
| Lazy pipelines over an `Ok` | [Ok.iterate][iterate] |
|
|
66
72
|
| Cached function calls | [memoized][memoized] |
|
|
67
73
|
| Step history or undo | [createStepper][stepper] |
|
|
68
|
-
|
|
|
69
|
-
| A lifecycle trigger
|
|
74
|
+
| Events between parts of an app | [Channel][channel] |
|
|
75
|
+
| A lifecycle trigger which fires once | [Signal][signal] |
|
|
70
76
|
|
|
71
77
|
## What's inside
|
|
72
78
|
|
|
73
79
|
### Error handling and flow
|
|
74
80
|
|
|
75
|
-
- [
|
|
76
|
-
- [
|
|
77
|
-
- [
|
|
78
|
-
- [
|
|
79
|
-
- [
|
|
80
|
-
- [
|
|
81
|
+
- [Result][result] (`Ok`, `Err`, expect, unwrap with else, andThen, mapErr, orFn, raw cause)
|
|
82
|
+
- [SafeTry][safe-try] (`safeTry`)
|
|
83
|
+
- [combineResults and combineErrors][combine] (`combineResults`, `combineErrors`)
|
|
84
|
+
- [Option][option] (`option`, Some, None, `isFalsy`, expect, unwrap with else, andThen)
|
|
85
|
+
- [Match][match] (`match`, `matchAll`)
|
|
86
|
+
- [Options][options] (`Options`)
|
|
87
|
+
- [Pipe][pipe] (`pipe`)
|
|
88
|
+
- [Panic][panic] (`panic`)
|
|
81
89
|
|
|
82
90
|
### Composition and conversion
|
|
83
91
|
|
|
84
|
-
- [Atom][atom]
|
|
85
|
-
- [To][to] (`.to()`
|
|
86
|
-
- [Zip][zip] (zip
|
|
92
|
+
- [Atom][atom] (`atom`)
|
|
93
|
+
- [To][to] (`.to()` on every wrapper, and `_to`)
|
|
94
|
+
- [Zip][zip] (`zip`, `zipWith`, `unzip`)
|
|
87
95
|
|
|
88
96
|
### Collections and data
|
|
89
97
|
|
|
90
|
-
- [list][list]
|
|
91
|
-
- [struct][struct]
|
|
92
|
-
- [sets][sets]
|
|
93
|
-
- [Ok.iterate][iterate]
|
|
94
|
-
- [memoized][memoized]
|
|
98
|
+
- [list][list] (`list`)
|
|
99
|
+
- [struct][struct] (`struct`)
|
|
100
|
+
- [sets][sets] (`sets`)
|
|
101
|
+
- [Ok.iterate][iterate] (`Ok(value).iterate()`)
|
|
102
|
+
- [memoized][memoized] (`memoized`)
|
|
95
103
|
|
|
96
104
|
### Async and lifecycle
|
|
97
105
|
|
|
98
|
-
- [SuperPromise][super-promise]
|
|
99
|
-
- [SuperRunner][super-runner]
|
|
100
|
-
- [taskGroup][task-group]
|
|
101
|
-
- [Signal][signal]
|
|
102
|
-
- [Channel][channel]
|
|
106
|
+
- [SuperPromise][super-promise] (`superPromise`)
|
|
107
|
+
- [SuperRunner][super-runner] (`superRunner`: sequence, all, race)
|
|
108
|
+
- [taskGroup][task-group] (`taskGroup`)
|
|
109
|
+
- [Signal][signal] (`createSignal`)
|
|
110
|
+
- [Channel][channel] (`createChannel`: send, subscribe, when)
|
|
111
|
+
- [createStepper][stepper] (`createStepper`)
|
|
103
112
|
|
|
104
113
|
### Small utilities
|
|
105
114
|
|
|
106
|
-
- [
|
|
107
|
-
- [println][logging] (with `setEnvironment`)
|
|
108
|
-
- [createStepper][stepper]
|
|
115
|
+
- [println][logging] (`println`, `setEnvironment`)
|
|
109
116
|
|
|
110
|
-
Full reference: [docs index][docs-index]. Feature list: [CHANGELOG.md][changelog]. Agent guide: [skill/SKILL.md]
|
|
117
|
+
Full reference: [docs index][docs-index]. Feature list: [CHANGELOG.md][changelog]. Agent guide: [skill/SKILL.md][skill].
|
|
111
118
|
|
|
112
119
|
## Real-world examples
|
|
113
120
|
|
|
114
|
-
### Load a value
|
|
121
|
+
### Load a value which can fail: SafeTry + Result
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
Loading saved settings can fail two ways: the saved text is not valid JSON, or the role inside
|
|
124
|
+
is one you do not support. `safeTry` turns both into one `Result`:
|
|
118
125
|
|
|
119
126
|
```ts
|
|
120
|
-
import {
|
|
127
|
+
import { Ok, Err, safeTry, type Result, println } from "slang-ts";
|
|
121
128
|
|
|
122
129
|
type Role = "admin" | "member";
|
|
123
130
|
|
|
131
|
+
type SavedSettings = {
|
|
132
|
+
role: string;
|
|
133
|
+
};
|
|
134
|
+
|
|
124
135
|
function parseRole(value: string): Result<Role, string> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
if (value === "admin" || value === "member") {
|
|
137
|
+
return Ok(value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return Err("Unknown role");
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
const savedText = '{"role":"admin"}';
|
|
144
|
+
|
|
145
|
+
const role = safeTry(() => {
|
|
146
|
+
const settings = JSON.parse(savedText) as SavedSettings;
|
|
147
|
+
|
|
148
|
+
return parseRole(settings.role);
|
|
134
149
|
});
|
|
135
150
|
|
|
136
151
|
if (role.isOk) {
|
|
137
|
-
println("Role:", role.value);
|
|
152
|
+
println("Role:", role.value); // "admin"
|
|
138
153
|
} else {
|
|
139
|
-
println("Could not load role:", role.error);
|
|
154
|
+
println("Could not load the role:", role.error);
|
|
140
155
|
}
|
|
141
156
|
```
|
|
142
157
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
type Role = "admin" | "member";
|
|
147
|
-
|
|
148
|
-
function loadRole(): Role {
|
|
149
|
-
const saved = localStorage.getItem("role");
|
|
150
|
-
if (saved === null) throw new Error("No role saved");
|
|
151
|
-
if (saved !== "admin" && saved !== "member") throw new Error("Unknown role");
|
|
152
|
-
return saved;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const role = loadRole();
|
|
157
|
-
console.log("Role:", role);
|
|
158
|
-
} catch (error) {
|
|
159
|
-
console.log(
|
|
160
|
-
"Could not load role:",
|
|
161
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
```
|
|
158
|
+
A throw from `JSON.parse` and an `Err` from `parseRole` both come back as `Err`, so you check
|
|
159
|
+
the result once. `safeTry` itself never throws. Your function is sync here, so the `Result`
|
|
160
|
+
comes back directly; an async function gives you a promise of it to `await`.
|
|
165
161
|
|
|
166
162
|
Full detail: [SafeTry][safe-try] and [Result][result].
|
|
167
163
|
|
|
168
164
|
### Call an API with a timeout and retries: SuperPromise
|
|
169
165
|
|
|
170
|
-
Your function
|
|
171
|
-
|
|
166
|
+
Your function receives an `AbortSignal` to pass along to `fetch`. `timeout` stops an attempt
|
|
167
|
+
once it takes too long, `retry` runs it again after a failure, and the call resolves to `Ok` or
|
|
168
|
+
`Err`. It never rejects:
|
|
172
169
|
|
|
173
170
|
```ts
|
|
174
171
|
import { superPromise, println } from "slang-ts";
|
|
175
172
|
|
|
176
|
-
|
|
173
|
+
type User = {
|
|
174
|
+
id: string;
|
|
175
|
+
name: string;
|
|
176
|
+
};
|
|
177
177
|
|
|
178
178
|
const user = await superPromise(
|
|
179
|
-
async (signal) => {
|
|
180
|
-
const response = await fetch(
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
async (signal: AbortSignal): Promise<User> => {
|
|
180
|
+
const response = await fetch("https://example.com/api/users/u_1", { signal });
|
|
181
|
+
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
throw new Error("Could not load the user");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return (await response.json()) as User;
|
|
183
187
|
},
|
|
184
|
-
{ timeout: 5000, retry: 2 },
|
|
188
|
+
{ timeout: 5000, retry: 2 }, // 5 seconds per attempt, up to 2 retries
|
|
185
189
|
);
|
|
186
190
|
|
|
187
191
|
if (user.isOk) {
|
|
188
|
-
println("User:", user.value.name);
|
|
192
|
+
println("User:", user.value.name); // "Ada"
|
|
189
193
|
} else {
|
|
190
|
-
println("Could not load user:", user.error);
|
|
194
|
+
println("Could not load the user:", user.error);
|
|
191
195
|
}
|
|
192
196
|
```
|
|
193
197
|
|
|
@@ -195,8 +199,8 @@ Full detail: [SuperPromise][super-promise].
|
|
|
195
199
|
|
|
196
200
|
### Page a list and shape records: list + struct
|
|
197
201
|
|
|
198
|
-
`list` keeps bounds strict: a
|
|
199
|
-
reads and shapes records
|
|
202
|
+
`list` keeps bounds strict: a range outside the array fails instead of returning fewer items. `struct`
|
|
203
|
+
reads and shapes records, keeping only the fields you choose.
|
|
200
204
|
|
|
201
205
|
```ts
|
|
202
206
|
import { list, struct, println } from "slang-ts";
|
|
@@ -208,25 +212,28 @@ type User = {
|
|
|
208
212
|
email: string;
|
|
209
213
|
};
|
|
210
214
|
|
|
211
|
-
const users: User[] =
|
|
215
|
+
const users: User[] = [
|
|
216
|
+
{ id: "u_1", name: "Ada", avatar: "/avatars/ada.png", email: "ada@example.com" },
|
|
217
|
+
{ id: "u_2", name: "Grace", email: "grace@example.com" },
|
|
218
|
+
{ id: "u_3", name: "Linus", email: "linus@example.com" },
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
const page = 1;
|
|
222
|
+
const size = 2;
|
|
212
223
|
|
|
213
|
-
const page = 2;
|
|
214
|
-
const size = 20;
|
|
215
224
|
const total = list(users).count().else(0);
|
|
216
225
|
const start = Math.min(page * size, total);
|
|
217
|
-
const
|
|
218
|
-
.range(start, Math.min(start + size, total))
|
|
219
|
-
.else([]);
|
|
226
|
+
const end = Math.min(start + size, total);
|
|
220
227
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
);
|
|
228
|
+
const paged = list(users).range(start, end).else([]);
|
|
229
|
+
|
|
230
|
+
const cards = paged.map((user) => struct(user).pick("id", "name", "avatar").else(user));
|
|
224
231
|
|
|
225
|
-
println(cards);
|
|
232
|
+
println(cards); // [{"id":"u_3","name":"Linus"}]
|
|
226
233
|
```
|
|
227
234
|
|
|
228
|
-
`list`
|
|
229
|
-
short. `struct` then keeps only the fields the card needs.
|
|
235
|
+
`list` never shortens a range, so the example clamps the window and lets the last page
|
|
236
|
+
be short. `struct` then keeps only the fields the card needs.
|
|
230
237
|
|
|
231
238
|
Full detail: [list][list] and [struct][struct].
|
|
232
239
|
|
|
@@ -248,7 +255,7 @@ projects using Slang.
|
|
|
248
255
|
|
|
249
256
|
## Contributing
|
|
250
257
|
|
|
251
|
-
Got a utility from another language
|
|
258
|
+
Got a utility from another language which belongs here? Open an issue or a pull request.
|
|
252
259
|
Contributions are welcome.
|
|
253
260
|
|
|
254
261
|
## Requirements
|
|
@@ -265,6 +272,7 @@ MIT License. See [LICENSE][license].
|
|
|
265
272
|
[option]: https://github.com/Hussseinkizz/slang/blob/main/docs/option.md
|
|
266
273
|
[result]: https://github.com/Hussseinkizz/slang/blob/main/docs/result.md
|
|
267
274
|
[safe-try]: https://github.com/Hussseinkizz/slang/blob/main/docs/safe-try.md
|
|
275
|
+
[combine]: https://github.com/Hussseinkizz/slang/blob/main/docs/combine.md
|
|
268
276
|
[match]: https://github.com/Hussseinkizz/slang/blob/main/docs/match.md
|
|
269
277
|
[options]: https://github.com/Hussseinkizz/slang/blob/main/docs/match.md#options
|
|
270
278
|
[pipe]: https://github.com/Hussseinkizz/slang/blob/main/docs/pipe.md
|
package/dist/index.d.ts
CHANGED
|
@@ -116,8 +116,52 @@ interface ResultMethods<T> {
|
|
|
116
116
|
* Ok(5).andThen(x => undefined); // Ok(5) - original
|
|
117
117
|
* await Ok(5).andThen(async x => x); // Ok(5)
|
|
118
118
|
*/
|
|
119
|
+
andThen<R extends PromiseLike<unknown>>(fn: (value: T) => R, ..._asyncOnly: AsyncCallbackGuard<R>): Result<SettledValue<Awaited<R>>, string> | Promise<Result<SettledValue<Awaited<R>>, string>>;
|
|
120
|
+
/**
|
|
121
|
+
* Sync callback overload: the result is a plain Result, so it can be read without `await`.
|
|
122
|
+
* The async overload above stays a union because a skipped `Err` returns itself
|
|
123
|
+
* synchronously; the callback never runs, so nothing can tell it was async.
|
|
124
|
+
*/
|
|
125
|
+
andThen<R>(fn: (value: T) => R, ..._syncOnly: SyncCallbackGuard<R>): Result<SettledValue<R>, string>;
|
|
126
|
+
/**
|
|
127
|
+
* Fallback for a callback that may or may not return a promise, such as
|
|
128
|
+
* `flag ? value : Promise.resolve(value)`, or whose return is `unknown`. Typed as the
|
|
129
|
+
* union so the promise case is not hidden.
|
|
130
|
+
*/
|
|
119
131
|
andThen<U>(fn: (value: T) => U | Result<U, string> | Promise<U | Result<U, string>>): Result<U, string> | Promise<Result<U, string>>;
|
|
132
|
+
/**
|
|
133
|
+
* Rewrites the error message if `Err`, returns the same `Ok` if `Ok`.
|
|
134
|
+
* Exists so a technical message can be made human at a boundary while `.raw` keeps the
|
|
135
|
+
* cause for logs. Sync only: rewriting a message never needs to wait.
|
|
136
|
+
* - If fn throws, returns `Err(message)` with the original `.raw`.
|
|
137
|
+
* @example
|
|
138
|
+
* Err("ECONNRESET", cause).mapErr(() => "Could not reach the server"); // .raw is still cause
|
|
139
|
+
* Ok(5).mapErr(() => "never runs"); // same Ok
|
|
140
|
+
*/
|
|
141
|
+
mapErr(fn: (error: string) => string): Result<T, string>;
|
|
142
|
+
/**
|
|
143
|
+
* Recovers from `Err`, returns the same `Ok` if `Ok`. The error-side mirror of `andThen`,
|
|
144
|
+
* named `orFn` so it is not confused with `unwrap().else()`.
|
|
145
|
+
* - `raw` is passed so recovery can inspect the cause, such as retrying only on a network failure.
|
|
146
|
+
* - A plain value becomes `Ok(value)`; a returned Result is passed through.
|
|
147
|
+
* - If fn returns `undefined`, returns the original `Err`.
|
|
148
|
+
* - If fn throws or rejects, returns `Err(message)`.
|
|
149
|
+
* @example
|
|
150
|
+
* Err("missing").orFn(() => 0); // Ok(0)
|
|
151
|
+
* Err("missing").orFn((e) => Err(`${e}!`)); // Err("missing!")
|
|
152
|
+
* await Err("offline").orFn(async () => cached); // Ok(cached)
|
|
153
|
+
*/
|
|
154
|
+
orFn<R extends PromiseLike<unknown>>(fn: (error: string, raw: unknown) => R, ..._asyncOnly: AsyncCallbackGuard<R>): Result<T | SettledValue<Awaited<R>>, string> | Promise<Result<T | SettledValue<Awaited<R>>, string>>;
|
|
155
|
+
/** Sync callback overload: the result is a plain Result, so it can be read without `await`. */
|
|
156
|
+
orFn<R>(fn: (error: string, raw: unknown) => R, ..._syncOnly: SyncCallbackGuard<R>): Result<T | SettledValue<R>, string>;
|
|
157
|
+
/**
|
|
158
|
+
* Fallback for a callback that may or may not return a promise, or whose return is
|
|
159
|
+
* `unknown`. Typed as the union so the promise case is not hidden.
|
|
160
|
+
*/
|
|
161
|
+
orFn<U>(fn: (error: string, raw: unknown) => U | Result<U, string> | Promise<U | Result<U, string>>): Result<T | U, string> | Promise<Result<T | U, string>>;
|
|
120
162
|
}
|
|
163
|
+
/** The value a callback's return settles to: a returned Result passes its value through. */
|
|
164
|
+
type SettledValue<R> = R extends ResultMethods<infer Value> ? Value : R;
|
|
121
165
|
/**
|
|
122
166
|
* Creates a new, successful result.
|
|
123
167
|
* @param value Value of the result. A Result is rejected; a Result never nests inside another Result.
|
|
@@ -236,15 +280,31 @@ interface OptionMethods<T> {
|
|
|
236
280
|
};
|
|
237
281
|
/**
|
|
238
282
|
* Transforms the inner value if `Some`, returns `None` if `None`.
|
|
283
|
+
* - A sync callback returns a plain Option, readable without `await`.
|
|
284
|
+
* - An async callback returns `Option | Promise<Option>`: a `None` skips the callback and
|
|
285
|
+
* returns itself synchronously, so `await` the result.
|
|
286
|
+
* - The returned value is wrapped with `option()`, not flattened: returning an Option gives
|
|
287
|
+
* `Some(thatOption)`, and a falsy value gives `None`.
|
|
239
288
|
* - If fn returns `undefined`, returns original Option (no new allocation).
|
|
240
|
-
* - If fn throws, returns `None`.
|
|
241
|
-
* - Supports async functions, returning `Promise<Option<U>>`.
|
|
289
|
+
* - If fn throws or rejects, returns `None`.
|
|
242
290
|
* @example
|
|
243
291
|
* option(5).andThen(x => x * 2); // Some(10)
|
|
244
292
|
* option(null).andThen(x => x * 2); // None
|
|
245
293
|
* option(5).andThen(x => undefined); // Some(5) - original
|
|
246
294
|
* await option(5).andThen(async x => x); // Some(5)
|
|
247
295
|
*/
|
|
296
|
+
andThen<R extends PromiseLike<unknown>>(fn: (value: T) => R, ..._asyncOnly: AsyncCallbackGuard<R>): Option<Awaited<R>> | Promise<Option<Awaited<R>>>;
|
|
297
|
+
/**
|
|
298
|
+
* Sync callback overload: the result is a plain Option, so it can be read without `await`.
|
|
299
|
+
* The async overload above stays a union because a `None` returns itself synchronously;
|
|
300
|
+
* the callback never runs, so nothing can tell it was async.
|
|
301
|
+
*/
|
|
302
|
+
andThen<R>(fn: (value: T) => R, ..._syncOnly: SyncCallbackGuard<R>): Option<R>;
|
|
303
|
+
/**
|
|
304
|
+
* Fallback for a callback that may or may not return a promise, such as
|
|
305
|
+
* `flag ? value : Promise.resolve(value)`, or whose return is `unknown`. Typed as the
|
|
306
|
+
* union so the promise case is not hidden.
|
|
307
|
+
*/
|
|
248
308
|
andThen<U>(fn: (value: T) => U | Promise<U>): Option<U> | Promise<Option<U>>;
|
|
249
309
|
}
|
|
250
310
|
/**
|
|
@@ -281,6 +341,20 @@ export declare function option<T>(value: T | NonTruthy): Option<T> & OptionMetho
|
|
|
281
341
|
* Used internally for deferred error handling in unwrap chains.
|
|
282
342
|
*/
|
|
283
343
|
export declare const scheduleMicrotask: (fn: () => void) => void;
|
|
344
|
+
/** Evaluated value result type */
|
|
345
|
+
/**
|
|
346
|
+
* Rest-parameter guard for a sync-callback overload: matches only callbacks whose return
|
|
347
|
+
* type is known to hold no promise. `unknown`, `any` and promise unions fall through to the
|
|
348
|
+
* union overload, because at runtime they may return either shape. `Rest` lets a caller add
|
|
349
|
+
* its own guard once the callback is known to be sync.
|
|
350
|
+
*/
|
|
351
|
+
type SyncCallbackGuard<T, Rest extends readonly string[] = []> = unknown extends T ? [never] : [Extract<T, PromiseLike<unknown>>] extends [never] ? Rest : [never];
|
|
352
|
+
/**
|
|
353
|
+
* Rest-parameter guard for a promise-callback overload: matches only real promise returns.
|
|
354
|
+
* A callback that only throws returns `never`, which is assignable to a promise type but
|
|
355
|
+
* runs synchronously, so it is sent to the sync overload.
|
|
356
|
+
*/
|
|
357
|
+
type AsyncCallbackGuard<R, Rest extends readonly string[] = []> = [R] extends [never] ? [never] : Rest;
|
|
284
358
|
//#endregion
|
|
285
359
|
//#region src/println.d.ts
|
|
286
360
|
type EnvironmentMode = "development" | "production" | "dev" | "prod";
|
|
@@ -293,12 +367,12 @@ type EnvironmentOptions = {
|
|
|
293
367
|
* Safe to call any number of times, in any order; the last call wins.
|
|
294
368
|
* Accepts a mode (full words or short forms: "development"/"dev", "production"/"prod")
|
|
295
369
|
* or options `{ printFn }` to route println through an existing logger instead of
|
|
296
|
-
* the console
|
|
370
|
+
* the console: useful in production, where logs go somewhere real.
|
|
297
371
|
* Pass `{ printFn: null }` to switch back to the console.
|
|
298
372
|
*/
|
|
299
373
|
export declare const setEnvironment: (next: EnvironmentMode | EnvironmentOptions) => void;
|
|
300
374
|
/**
|
|
301
|
-
* Prints a line to the console
|
|
375
|
+
* Prints a line to the console: args joined with spaces, newline appended.
|
|
302
376
|
* When a custom printFn is set, the line goes to it instead, in every mode.
|
|
303
377
|
* Otherwise production mode is silent, so dev chatter never leaks to prod logs.
|
|
304
378
|
*/
|
|
@@ -317,6 +391,28 @@ export declare const println: (...args: unknown[]) => void;
|
|
|
317
391
|
*/
|
|
318
392
|
export declare function panic(message: string): never;
|
|
319
393
|
//#endregion
|
|
394
|
+
//#region src/combine.d.ts
|
|
395
|
+
/** Maps a tuple or array of Results to the tuple or array of their success values. */
|
|
396
|
+
type CombinedValues<R extends readonly Result<unknown, string>[]> = { -readonly [K in keyof R]: R[K] extends ResultMethods<infer Value> ? Value : never; };
|
|
397
|
+
/**
|
|
398
|
+
* Combines results you already have into one Result of all their values.
|
|
399
|
+
* Stops at the first failure and returns that same `Err` instance, so its `.raw` cause is
|
|
400
|
+
* kept. Sync by design: dependent steps belong in `pipe`, async tasks in `superRunner`.
|
|
401
|
+
* @example
|
|
402
|
+
* combineResults([Ok(1), Ok("a")]); // Ok([1, "a"]), typed Result<[number, string], string>
|
|
403
|
+
* combineResults([Ok(1), Err("bad")]); // the Err("bad") instance
|
|
404
|
+
* combineResults([]); // Ok([])
|
|
405
|
+
*/
|
|
406
|
+
export declare function combineResults<R extends readonly Result<unknown, string>[]>(results: readonly [...R]): Result<CombinedValues<R>, string>;
|
|
407
|
+
/**
|
|
408
|
+
* Collects every error message, in input order, so a form can show all problems at once
|
|
409
|
+
* instead of only the first. Returns `[]` when every result is `Ok`.
|
|
410
|
+
* @example
|
|
411
|
+
* combineErrors([Err("Enter a valid email"), Ok("Ada"), Err("Name is required")]);
|
|
412
|
+
* // ["Enter a valid email", "Name is required"]
|
|
413
|
+
*/
|
|
414
|
+
export declare function combineErrors(results: readonly Result<unknown, string>[]): string[];
|
|
415
|
+
//#endregion
|
|
320
416
|
//#region src/options.d.ts
|
|
321
417
|
/**
|
|
322
418
|
* Resolves the usable inner type of a value.
|
|
@@ -339,8 +435,8 @@ type OptionsValue<T> = T extends {
|
|
|
339
435
|
* Returns the first usable value from a list as an `Ok`, or the fallback
|
|
340
436
|
* `Err` if none is usable.
|
|
341
437
|
*
|
|
342
|
-
* Normalizes mixed values
|
|
343
|
-
* from different sources
|
|
438
|
+
* Normalizes mixed values: raw values, `Option`s, and `Result`s
|
|
439
|
+
* from different sources: when all you care about is whether any of them
|
|
344
440
|
* is usable. A value is usable when its unwrapped inner value is truthy;
|
|
345
441
|
* `None`, `Err`, null, undefined, `""`, NaN, and Infinity are skipped.
|
|
346
442
|
* Note that `0` and `false` count as usable, just as they do in `option()`.
|
|
@@ -375,7 +471,7 @@ export declare function _to<E = string>(value: Atom<any>, target: "result"): Res
|
|
|
375
471
|
//#endregion
|
|
376
472
|
//#region src/match.d.ts
|
|
377
473
|
/**
|
|
378
|
-
* Pattern matching for `Result` and `Option
|
|
474
|
+
* Pattern matching for `Result` and `Option`: exhaustiveness enforced.
|
|
379
475
|
*
|
|
380
476
|
* Returns the value returned by the selected handler. If all handlers return
|
|
381
477
|
* `Result` or `Option`, TypeScript will infer that automatically.
|
|
@@ -496,29 +592,34 @@ type SafeTryOptions = {
|
|
|
496
592
|
};
|
|
497
593
|
/**
|
|
498
594
|
* Captures failures and resolves nested Slang wrappers into one Result.
|
|
499
|
-
* -
|
|
595
|
+
* - A sync callback gets its Result back directly; a promise-returning callback
|
|
596
|
+
* gets a Promise of the Result. `await` works on both, so callers need not know.
|
|
597
|
+
* - Iteratively unwraps Result, Option, and Atom values.
|
|
500
598
|
* - Returns the first Err or None as Err; cycles also return Err.
|
|
501
599
|
* - Leaves object properties, array elements, and wrapped Promise values intact.
|
|
502
|
-
* - `{ throw: true }` re-throws thrown errors
|
|
600
|
+
* - `{ throw: true }` re-throws thrown errors (synchronously for a sync callback,
|
|
601
|
+
* as a rejection for an async one); returned failures remain Err.
|
|
503
602
|
* - `{ collectValues: true }` records every unwrapped layer and failure on
|
|
504
603
|
* `result.raw` as `{ errors, values }`.
|
|
505
604
|
* - A caught throw reaches the caller as `Err(message)`, with the thrown value on `.raw`.
|
|
506
605
|
*
|
|
507
606
|
* @param fn - Function to execute (sync or async)
|
|
508
607
|
* @param options - `{ throw?: boolean; collectValues?: boolean }`
|
|
509
|
-
* @returns Promise of one Result
|
|
608
|
+
* @returns One Result for a sync callback, a Promise of one Result for an async one
|
|
510
609
|
*
|
|
511
610
|
* @example
|
|
512
|
-
* const result =
|
|
611
|
+
* const result = safeTry(() => Ok(option("Hello")));
|
|
513
612
|
* if (result.isOk) println(result.value); // "Hello"
|
|
514
613
|
*
|
|
515
614
|
* @example
|
|
516
|
-
* const result = await safeTry(() => {
|
|
615
|
+
* const result = await safeTry(async () => {
|
|
517
616
|
* throw new Error("Oops!");
|
|
518
617
|
* });
|
|
519
618
|
* if (result.isErr) println(result.error);
|
|
520
619
|
*/
|
|
521
|
-
export declare function safeTry<
|
|
620
|
+
export declare function safeTry<R extends PromiseLike<unknown>>(fn: () => R, options?: SafeTryOptions, ..._noNestedResult: AsyncCallbackGuard<R, NestedValueGuard<Awaited<R>>>): Promise<Result<SafeTryValue<Awaited<R>>, string>>;
|
|
621
|
+
export declare function safeTry<T>(fn: () => T, options?: SafeTryOptions, ..._noNestedResult: SyncCallbackGuard<T, NestedValueGuard<T>>): Result<SafeTryValue<T>, string>;
|
|
622
|
+
export declare function safeTry<T>(fn: () => T | PromiseLike<T>, options?: SafeTryOptions, ..._noNestedResult: NestedValueGuard<T>): Result<SafeTryValue<T>, string> | Promise<Result<SafeTryValue<T>, string>>;
|
|
522
623
|
//#endregion
|
|
523
624
|
//#region src/list.d.ts
|
|
524
625
|
/** Accepted list input: a raw array, or a narrowed successful `Ok` holding one. */
|
|
@@ -773,7 +874,7 @@ export declare function pipe<T, E = string>(initial: T, ...fns: PipeFn[]): Pipel
|
|
|
773
874
|
type Executor<T> = (signal: AbortSignal) => T | Promise<T> | Result<T, string> | Promise<Result<T, string>>;
|
|
774
875
|
/** Stop signal for `retryUntil`. */
|
|
775
876
|
type RetryUntilOptions = {
|
|
776
|
-
/** When this fires, retrying ends
|
|
877
|
+
/** When this fires, retrying ends: mid-attempt firings cancel the in-flight attempt. */
|
|
777
878
|
signal: AbortSignal;
|
|
778
879
|
/** Wait between attempts. No wait applies before the first attempt. */
|
|
779
880
|
delay?: number;
|
|
@@ -786,7 +887,7 @@ type SuperPromiseOptions = {
|
|
|
786
887
|
delay?: number;
|
|
787
888
|
/** Abort the operation after this many milliseconds. Timeouts are never retried. */
|
|
788
889
|
timeout?: number;
|
|
789
|
-
/** `n` retries after the first attempt
|
|
890
|
+
/** `n` retries after the first attempt: `n + 1` attempts total. Aborts and timeouts are never retried. */
|
|
790
891
|
retry?: number;
|
|
791
892
|
/** Keep retrying until the stop signal fires. Overrides `retry` and `delay`. */
|
|
792
893
|
retryUntil?: RetryUntilOptions;
|
|
@@ -807,13 +908,13 @@ type SuperPromise<T> = {
|
|
|
807
908
|
tap(fn: (value: T) => unknown | Promise<unknown>): SuperPromise<T>;
|
|
808
909
|
/** Promise-compatible. The callback receives the settled `Result`; the task never rejects. */
|
|
809
910
|
then<TR1 = Result<T, string>, TR2 = never>(onfulfilled?: ((value: Result<T, string>) => TR1 | PromiseLike<TR1>) | null, onrejected?: ((reason: unknown) => TR2 | PromiseLike<TR2>) | null): Promise<TR1 | TR2>;
|
|
810
|
-
/** Promise-compatible. Effectively dead
|
|
911
|
+
/** Promise-compatible. Effectively dead: failures are captured into `Err`, never rejections. */
|
|
811
912
|
catch<TR = never>(onrejected?: ((reason: unknown) => TR | PromiseLike<TR>) | null): Promise<Result<T, string> | TR>;
|
|
812
913
|
/** Promise-compatible. Native behavior. */
|
|
813
914
|
finally(onfinally?: (() => void) | null): Promise<Result<T, string>>;
|
|
814
915
|
};
|
|
815
916
|
/**
|
|
816
|
-
* Creates a SuperPromise
|
|
917
|
+
* Creates a SuperPromise: one async operation plus start, retry, timeout, and cancel controls.
|
|
817
918
|
*
|
|
818
919
|
* Lazy start: your function runs on first use (`await`/`.then()`, or a runner's `start()`),
|
|
819
920
|
* not at creation. Defer chains start recursively, so awaiting a deferred task runs its
|
|
@@ -891,7 +992,7 @@ type Channel = {
|
|
|
891
992
|
}): void;
|
|
892
993
|
};
|
|
893
994
|
/**
|
|
894
|
-
* Creates a Channel
|
|
995
|
+
* Creates a Channel: completely independent of SuperPromise/SuperRunner.
|
|
895
996
|
*
|
|
896
997
|
* @example
|
|
897
998
|
* const channel = createChannel();
|
|
@@ -907,7 +1008,7 @@ export declare function createChannel(): Channel;
|
|
|
907
1008
|
//#endregion
|
|
908
1009
|
//#region src/signal.d.ts
|
|
909
1010
|
/**
|
|
910
|
-
* A lifecycle Signal
|
|
1011
|
+
* A lifecycle Signal: a trigger that fires once and stays fired.
|
|
911
1012
|
*
|
|
912
1013
|
* ... structurally an `AbortSignal` plus a `fire()` method,
|
|
913
1014
|
* so it can be passed anywhere a native `AbortSignal` is expected
|
|
@@ -933,9 +1034,9 @@ type Signal = AbortSignal & {
|
|
|
933
1034
|
* signal.aborted; // true
|
|
934
1035
|
* ```
|
|
935
1036
|
*
|
|
936
|
-
* Listeners are NOT replayed after firing
|
|
1037
|
+
* Listeners are NOT replayed after firing: consumers must check
|
|
937
1038
|
* `signal.aborted` synchronously to detect an already-fired signal.
|
|
938
1039
|
*/
|
|
939
1040
|
export declare function createSignal(): Signal;
|
|
940
1041
|
//#endregion
|
|
941
|
-
export type { Atom, AtomMethods, Channel, ChannelEvent, ChannelSubscriber, EnvironmentMode, EnvironmentOptions, Err as ErrType, Executor, Fallible, IterateChain, ListChain, ListGet, ListInput, Memoized, NextOptions, NonTruthy, None, Ok as OkType, Option, OptionMethods, OptionsValue, PipeEachContext, PipeFn, PipeRunOptions, Pipeline, PrevRecover, PrevResolution, PrintFn, Result, ResultMethods, RetryUntilOptions, SetsChain, SetsInput, Signal, SlangErrorCategory, Some, Step, StepFactory, StepFn, Stepper, StepperConfig, StructChain, StructGet, SuperPromise, SuperPromiseOptions, SuperRunner, TaskGroup, TaskGroupOptions };
|
|
1042
|
+
export type { Atom, AtomMethods, Channel, ChannelEvent, ChannelSubscriber, CombinedValues, EnvironmentMode, EnvironmentOptions, Err as ErrType, Executor, Fallible, IterateChain, ListChain, ListGet, ListInput, Memoized, NextOptions, NonTruthy, None, Ok as OkType, Option, OptionMethods, OptionsValue, PipeEachContext, PipeFn, PipeRunOptions, Pipeline, PrevRecover, PrevResolution, PrintFn, Result, ResultMethods, RetryUntilOptions, SetsChain, SetsInput, Signal, SlangErrorCategory, Some, Step, StepFactory, StepFn, Stepper, StepperConfig, StructChain, StructGet, SuperPromise, SuperPromiseOptions, SuperRunner, TaskGroup, TaskGroupOptions };
|