@fulcro/functions 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 +15 -0
- package/README.md +165 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +14 -0
- package/dist/switchFor/index.d.ts +128 -0
- package/dist/switchFor/index.js +12 -0
- package/dist/tryCatch/index.d.ts +77 -0
- package/dist/tryCatch/index.js +62 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 diguu <rodrigogeribola@hotmail.com>
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# @fulcro/functions
|
|
2
|
+
|
|
3
|
+
Two runtime helpers that turn control flow into values. No dependencies, no
|
|
4
|
+
compiler involvement, nothing to configure.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { switchFor, tryCatch } from '@fulcro/functions';
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## `switchFor`
|
|
11
|
+
|
|
12
|
+
Choosing between branches, as an expression rather than as a statement. It comes
|
|
13
|
+
in two forms, and which one to reach for depends on whether the value is drawn
|
|
14
|
+
from a closed set.
|
|
15
|
+
|
|
16
|
+
### Exhaustive form — enums and literal unions
|
|
17
|
+
|
|
18
|
+
Pass one branch per member, and the compiler enforces that every member has one.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
enum Status {
|
|
22
|
+
Draft,
|
|
23
|
+
Published,
|
|
24
|
+
Archived,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const label = switchFor(status, {
|
|
28
|
+
[Status.Draft]: () => 'draft',
|
|
29
|
+
[Status.Published]: () => 'published',
|
|
30
|
+
[Status.Archived]: () => 'archived',
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Leave a member out and it does not compile:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
error TS2345: Property '[Status.Archived]' is missing in type
|
|
38
|
+
'{ 0: () => string; 1: () => string; }' but required in type
|
|
39
|
+
'ExhaustiveCases<Status, string>'.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**That error is the whole point.** When a member is added to the enum later,
|
|
43
|
+
every `switchFor` over it stops compiling until the new case is handled — which
|
|
44
|
+
is the moment to decide what it should do, rather than finding the gap in
|
|
45
|
+
production. A native `switch` says nothing in that situation; it just falls
|
|
46
|
+
through.
|
|
47
|
+
|
|
48
|
+
There is deliberately **no fallback parameter** in this form. A fallback is
|
|
49
|
+
precisely what would absorb the new member in silence and take the guarantee
|
|
50
|
+
away.
|
|
51
|
+
|
|
52
|
+
Each branch receives the single member it handles, already narrowed:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
switchFor(status, {
|
|
56
|
+
[Status.Draft]: (value) => {
|
|
57
|
+
const draft: Status.Draft = value; // not Status
|
|
58
|
+
return render(draft);
|
|
59
|
+
},
|
|
60
|
+
// …
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Works with numeric enums, string enums, and unions of string or number
|
|
65
|
+
literals. A member whose value is `0` is dispatched like any other — the branch
|
|
66
|
+
is found by key, never by testing the value for truthiness.
|
|
67
|
+
|
|
68
|
+
### Running it for its effects
|
|
69
|
+
|
|
70
|
+
There is no separate void-returning function, and none is needed. Where the
|
|
71
|
+
branches return nothing, `R` is inferred as `void` and the call stands on its
|
|
72
|
+
own as a statement:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
switchFor(status, {
|
|
76
|
+
[Status.Draft]: () => saveDraft(),
|
|
77
|
+
[Status.Published]: () => publish(),
|
|
78
|
+
[Status.Archived]: () => archive(),
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The exhaustiveness check applies there exactly as it does to a call whose result
|
|
83
|
+
is read — leave a member out and this fails to compile too.
|
|
84
|
+
|
|
85
|
+
### Predicate form — everything else
|
|
86
|
+
|
|
87
|
+
Where branches are conditions rather than values:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const size = switchFor(
|
|
91
|
+
order,
|
|
92
|
+
[
|
|
93
|
+
{ when: (o) => o.total > 1000, then: () => 'large' },
|
|
94
|
+
{ when: (o) => o.items.length === 0, then: () => 'empty' },
|
|
95
|
+
],
|
|
96
|
+
() => 'standard',
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Branches are tested in order, the first match wins, and the rest are never
|
|
101
|
+
evaluated — neither their conditions nor their bodies.
|
|
102
|
+
|
|
103
|
+
`otherwise` is optional, and leaving it out shows up in the type instead of
|
|
104
|
+
being hidden:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const a = switchFor(n, cases); // R | undefined
|
|
108
|
+
const b = switchFor(n, cases, () => fallback); // R
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Without a fallback, an unmatched value evaluates to `undefined` and the compiler
|
|
112
|
+
makes you account for it. With one, the `undefined` is gone, because nothing can
|
|
113
|
+
produce it any more.
|
|
114
|
+
|
|
115
|
+
This form **cannot** be exhaustive. A condition is an arbitrary function, and
|
|
116
|
+
the compiler cannot reason about which values it accepts — which is why it takes
|
|
117
|
+
a fallback and the exhaustive form does not. It also decides a branch without
|
|
118
|
+
narrowing the value inside `then`.
|
|
119
|
+
|
|
120
|
+
## `tryCatch`
|
|
121
|
+
|
|
122
|
+
The outcome of an operation as a value, instead of as control flow.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const result = await tryCatch(() => fetch(url));
|
|
126
|
+
|
|
127
|
+
if (result.error !== null) return fallback;
|
|
128
|
+
|
|
129
|
+
use(result.data);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Prefer the callback form.** Passing a promise that already exists cannot catch
|
|
133
|
+
anything the expression throws on its way to producing it — in
|
|
134
|
+
`tryCatch(risky())`, `risky` runs first, and a synchronous throw inside it
|
|
135
|
+
escapes before `tryCatch` is ever called. The callback form moves that call
|
|
136
|
+
inside the `try`, which is the only way to cover both the synchronous and the
|
|
137
|
+
asynchronous failure of one operation. The promise form is still accepted, and
|
|
138
|
+
reads better when the promise is already in hand.
|
|
139
|
+
|
|
140
|
+
**Discriminate on `error`, never on `data`.** `0`, `''` and `null` are perfectly
|
|
141
|
+
good results, and `if (result.data)` reports every one of them as a failure.
|
|
142
|
+
Checking `result.error === null` narrows the union properly:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
if (result.error === null) {
|
|
146
|
+
result.data; // T, not T | null
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**`E` defaults to `unknown`, not to `Error`.** JavaScript lets any value be
|
|
151
|
+
thrown, so typing the error as an `Error` would be a claim this function cannot
|
|
152
|
+
keep — `result.error.message` would read `undefined` whenever something threw a
|
|
153
|
+
string. Narrow it at the use site, or pass the type explicitly when you own
|
|
154
|
+
every throw site:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const result = await tryCatch<User, ApiError>(() => api.load(id));
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
A thrown `null` or `undefined` — legal, however pathological — is wrapped in an
|
|
161
|
+
`Error` carrying the original value as its `cause`, because storing it as it
|
|
162
|
+
came would make the failure indistinguishable from a success.
|
|
163
|
+
|
|
164
|
+
`tryCatch` always returns a promise, including for a fully synchronous
|
|
165
|
+
operation.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry point of the package, matching the `main` and `types` fields of
|
|
3
|
+
* `package.json`.
|
|
4
|
+
*
|
|
5
|
+
* Listed one by one rather than re-exported wholesale, so that adding an export
|
|
6
|
+
* to a module below is never enough on its own to put it in front of consumers.
|
|
7
|
+
*/
|
|
8
|
+
export { type ExhaustiveCases, type SwitchCase, switchFor } from './switchFor/index.js';
|
|
9
|
+
export { type Failure, type Result, type Success, tryCatch } from './tryCatch/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryCatch = exports.switchFor = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Public entry point of the package, matching the `main` and `types` fields of
|
|
6
|
+
* `package.json`.
|
|
7
|
+
*
|
|
8
|
+
* Listed one by one rather than re-exported wholesale, so that adding an export
|
|
9
|
+
* to a module below is never enough on its own to put it in front of consumers.
|
|
10
|
+
*/
|
|
11
|
+
var switchFor_1 = require("./switchFor/index.js");
|
|
12
|
+
Object.defineProperty(exports, "switchFor", { enumerable: true, get: function () { return switchFor_1.switchFor; } });
|
|
13
|
+
var tryCatch_1 = require("./tryCatch/index.js");
|
|
14
|
+
Object.defineProperty(exports, "tryCatch", { enumerable: true, get: function () { return tryCatch_1.tryCatch; } });
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One branch of the predicate form of {@link switchFor}.
|
|
3
|
+
*
|
|
4
|
+
* @template T Type of the evaluated value.
|
|
5
|
+
* @template R Type produced by the branch.
|
|
6
|
+
*/
|
|
7
|
+
export interface SwitchCase<T, R> {
|
|
8
|
+
/**
|
|
9
|
+
* Condition deciding whether this branch handles the value.
|
|
10
|
+
*
|
|
11
|
+
* @param value Value being evaluated.
|
|
12
|
+
* @returns `true` when this branch handles it.
|
|
13
|
+
*/
|
|
14
|
+
readonly when: (value: T) => boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Produces the result of this branch.
|
|
17
|
+
*
|
|
18
|
+
* Only called for the branch that matched, so a branch may do work that
|
|
19
|
+
* would be wrong or expensive for a value it does not handle.
|
|
20
|
+
*
|
|
21
|
+
* @param value Value being evaluated.
|
|
22
|
+
* @returns The result of the branch.
|
|
23
|
+
*/
|
|
24
|
+
readonly then: (value: T) => R;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A branch for every member of a closed set of values.
|
|
28
|
+
*
|
|
29
|
+
* Written as a mapped type over the union rather than as an index signature,
|
|
30
|
+
* which is what makes the exhaustiveness a compile error: a missing member is a
|
|
31
|
+
* missing required property, and an unknown one has nothing to be assigned to.
|
|
32
|
+
*
|
|
33
|
+
* Each branch receives the single member it handles, not the whole union, so
|
|
34
|
+
* the value arrives already narrowed.
|
|
35
|
+
*
|
|
36
|
+
* @template T Union of the values being matched, typically an enum.
|
|
37
|
+
* @template R Type produced by every branch.
|
|
38
|
+
*/
|
|
39
|
+
export type ExhaustiveCases<T extends PropertyKey, R> = {
|
|
40
|
+
readonly [K in T]: (value: K) => R;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Chooses between branches, as an expression.
|
|
44
|
+
*
|
|
45
|
+
* Comes in two forms, and which one to reach for depends on whether the value
|
|
46
|
+
* being matched is drawn from a closed set.
|
|
47
|
+
*
|
|
48
|
+
* **Exhaustive form** — for an enum, or any union of string or number literals.
|
|
49
|
+
* Pass one branch per member and the compiler enforces that every member has
|
|
50
|
+
* one:
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* enum Status {
|
|
54
|
+
* Draft,
|
|
55
|
+
* Published,
|
|
56
|
+
* Archived,
|
|
57
|
+
* }
|
|
58
|
+
*
|
|
59
|
+
* const label = switchFor(status, {
|
|
60
|
+
* [Status.Draft]: () => 'draft',
|
|
61
|
+
* [Status.Published]: () => 'published',
|
|
62
|
+
* [Status.Archived]: () => 'archived',
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* Leaving a member out does not compile, and neither does adding a branch for
|
|
67
|
+
* something that is not a member. That is the point of this form: when a
|
|
68
|
+
* member is added to the enum later, every `switchFor` over it stops compiling
|
|
69
|
+
* until it is handled — which is exactly the moment to decide what it should
|
|
70
|
+
* do, rather than discovering the gap at runtime. There is deliberately no
|
|
71
|
+
* fallback parameter here, because a fallback is precisely what would absorb
|
|
72
|
+
* the new member in silence and take the guarantee away.
|
|
73
|
+
*
|
|
74
|
+
* **Predicate form** — for anything else, where branches are conditions rather
|
|
75
|
+
* than values:
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* const size = switchFor(
|
|
79
|
+
* order,
|
|
80
|
+
* [
|
|
81
|
+
* { when: (o) => o.total > 1000, then: () => 'large' },
|
|
82
|
+
* { when: (o) => o.items.length === 0, then: () => 'empty' },
|
|
83
|
+
* ],
|
|
84
|
+
* () => 'standard',
|
|
85
|
+
* );
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* Branches are tested in order and the first match wins; the rest are never
|
|
89
|
+
* evaluated, neither their conditions nor their bodies.
|
|
90
|
+
*
|
|
91
|
+
* `otherwise` is optional, and leaving it out is reflected in the type rather
|
|
92
|
+
* than hidden: the call then evaluates to `R | undefined`, so the compiler
|
|
93
|
+
* makes the caller account for the value that matched nothing. Passing a
|
|
94
|
+
* fallback removes the `undefined`, because nothing can produce it any more.
|
|
95
|
+
*
|
|
96
|
+
* The predicate form cannot be exhaustive. A condition is an arbitrary function
|
|
97
|
+
* and the compiler cannot reason about which values it accepts, which is why
|
|
98
|
+
* only this form has a fallback at all. It also decides a branch without
|
|
99
|
+
* narrowing the value inside `then`, where the exhaustive form hands each
|
|
100
|
+
* branch the single member it handles.
|
|
101
|
+
*
|
|
102
|
+
* **Using it for its effects needs no separate function.** Where the branches
|
|
103
|
+
* return nothing, `R` is inferred as `void` and the call stands on its own as a
|
|
104
|
+
* statement:
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* switchFor(status, {
|
|
108
|
+
* [Status.Draft]: () => saveDraft(),
|
|
109
|
+
* [Status.Published]: () => publish(),
|
|
110
|
+
* [Status.Archived]: () => archive(),
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* The exhaustiveness check applies there exactly as it does to a call whose
|
|
115
|
+
* result is read.
|
|
116
|
+
*
|
|
117
|
+
* @template T Type of the evaluated value.
|
|
118
|
+
* @template R Type produced by every branch.
|
|
119
|
+
* @param value Value being evaluated.
|
|
120
|
+
* @param cases One branch per member, or branches tested in order.
|
|
121
|
+
* @param otherwise Produces the result when no branch matches. Predicate form
|
|
122
|
+
* only; omitting it admits `undefined` into the result.
|
|
123
|
+
* @returns The result of the branch that handled the value, the result of
|
|
124
|
+
* `otherwise`, or `undefined` when nothing matched and no fallback was given.
|
|
125
|
+
*/
|
|
126
|
+
export declare function switchFor<T extends PropertyKey, R>(value: T, cases: ExhaustiveCases<T, R>): R;
|
|
127
|
+
export declare function switchFor<T, R>(value: T, cases: readonly SwitchCase<T, R>[], otherwise: (value: T) => R): R;
|
|
128
|
+
export declare function switchFor<T, R>(value: T, cases: readonly SwitchCase<T, R>[]): R | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.switchFor = switchFor;
|
|
4
|
+
function switchFor(value, cases, otherwise) {
|
|
5
|
+
if (!Array.isArray(cases)) {
|
|
6
|
+
return cases[value](value);
|
|
7
|
+
}
|
|
8
|
+
const matched = cases.find(({ when }) => when(value));
|
|
9
|
+
if (matched !== undefined)
|
|
10
|
+
return matched.then(value);
|
|
11
|
+
return otherwise === undefined ? undefined : otherwise(value);
|
|
12
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Outcome of an operation that produced a value. */
|
|
2
|
+
export interface Success<T> {
|
|
3
|
+
/** Value the operation produced. */
|
|
4
|
+
readonly data: T;
|
|
5
|
+
/** Always `null`, which is what tells a success from a failure. */
|
|
6
|
+
readonly error: null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Outcome of an operation that threw.
|
|
10
|
+
*
|
|
11
|
+
* The error is non nullable by construction, so that `error === null` is enough
|
|
12
|
+
* to tell the two cases apart. {@link tryCatch} upholds that at runtime: a
|
|
13
|
+
* thrown `null` or `undefined` — legal in JavaScript, however pathological — is
|
|
14
|
+
* wrapped in an `Error` rather than stored as is, because storing it would make
|
|
15
|
+
* a failure indistinguishable from a success.
|
|
16
|
+
*
|
|
17
|
+
* @template E Type of the captured error.
|
|
18
|
+
*/
|
|
19
|
+
export interface Failure<E> {
|
|
20
|
+
/** Always `null`, since the operation produced no value. */
|
|
21
|
+
readonly data: null;
|
|
22
|
+
/** The captured error. */
|
|
23
|
+
readonly error: NonNullable<E>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The outcome of an operation, as a value rather than as control flow.
|
|
27
|
+
*
|
|
28
|
+
* Discriminate on `error`, never on `data`:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* if (result.error === null) use(result.data);
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* `data` is a valid discriminant only while the success type excludes every
|
|
35
|
+
* falsy value, which is a property of `T` and not of this type — `0`, `''` and
|
|
36
|
+
* `null` are all perfectly good results, and `if (result.data)` reports each of
|
|
37
|
+
* them as a failure.
|
|
38
|
+
*
|
|
39
|
+
* @template T Type produced on success.
|
|
40
|
+
* @template E Type of the error captured on failure.
|
|
41
|
+
*/
|
|
42
|
+
export type Result<T, E = unknown> = Success<T> | Failure<E>;
|
|
43
|
+
/**
|
|
44
|
+
* Runs an operation and returns its outcome instead of throwing.
|
|
45
|
+
*
|
|
46
|
+
* ```ts
|
|
47
|
+
* const result = await tryCatch(() => fetch(url));
|
|
48
|
+
*
|
|
49
|
+
* if (result.error !== null) return fallback;
|
|
50
|
+
*
|
|
51
|
+
* use(result.data);
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* **Prefer the callback form.** Passing a promise that already exists cannot
|
|
55
|
+
* catch anything the expression throws on its way to producing it: in
|
|
56
|
+
* `tryCatch(risky())`, `risky` runs first, and a synchronous throw inside it
|
|
57
|
+
* escapes before this function is ever called. The callback form moves that
|
|
58
|
+
* call inside the `try`, which is the only way to cover both the synchronous
|
|
59
|
+
* and the asynchronous failure of the same operation. The promise form is kept
|
|
60
|
+
* because it reads better when the promise is already in hand.
|
|
61
|
+
*
|
|
62
|
+
* `E` defaults to `unknown` rather than to `Error`, deliberately. JavaScript
|
|
63
|
+
* lets any value be thrown, and typing the error as an `Error` without checking
|
|
64
|
+
* would be a claim this function cannot keep — `result.error.message` would
|
|
65
|
+
* then read `undefined` whenever something threw a string. Narrow it at the use
|
|
66
|
+
* site, or pass the type explicitly when you own every throw site.
|
|
67
|
+
*
|
|
68
|
+
* Always returns a promise, including for an operation that is entirely
|
|
69
|
+
* synchronous.
|
|
70
|
+
*
|
|
71
|
+
* @template T Type produced by the operation.
|
|
72
|
+
* @template E Type of the error captured on failure.
|
|
73
|
+
* @param operation Callback performing the operation, or a promise already
|
|
74
|
+
* running it.
|
|
75
|
+
* @returns The outcome of the operation.
|
|
76
|
+
*/
|
|
77
|
+
export declare const tryCatch: <T, E = unknown>(operation: Promise<T> | (() => T | PromiseLike<T>)) => Promise<Result<Awaited<T>, E>>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryCatch = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Something a thrown value can be stored as without defeating the discriminant.
|
|
6
|
+
*
|
|
7
|
+
* @param error Value that was thrown.
|
|
8
|
+
* @returns The value itself, or an `Error` standing in for a nullish throw.
|
|
9
|
+
*/
|
|
10
|
+
const asStorableError = (error) => {
|
|
11
|
+
if (error !== null && error !== undefined)
|
|
12
|
+
return error;
|
|
13
|
+
return new Error(`Operation rejected with ${String(error)}`, {
|
|
14
|
+
cause: error,
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Runs an operation and returns its outcome instead of throwing.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const result = await tryCatch(() => fetch(url));
|
|
22
|
+
*
|
|
23
|
+
* if (result.error !== null) return fallback;
|
|
24
|
+
*
|
|
25
|
+
* use(result.data);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* **Prefer the callback form.** Passing a promise that already exists cannot
|
|
29
|
+
* catch anything the expression throws on its way to producing it: in
|
|
30
|
+
* `tryCatch(risky())`, `risky` runs first, and a synchronous throw inside it
|
|
31
|
+
* escapes before this function is ever called. The callback form moves that
|
|
32
|
+
* call inside the `try`, which is the only way to cover both the synchronous
|
|
33
|
+
* and the asynchronous failure of the same operation. The promise form is kept
|
|
34
|
+
* because it reads better when the promise is already in hand.
|
|
35
|
+
*
|
|
36
|
+
* `E` defaults to `unknown` rather than to `Error`, deliberately. JavaScript
|
|
37
|
+
* lets any value be thrown, and typing the error as an `Error` without checking
|
|
38
|
+
* would be a claim this function cannot keep — `result.error.message` would
|
|
39
|
+
* then read `undefined` whenever something threw a string. Narrow it at the use
|
|
40
|
+
* site, or pass the type explicitly when you own every throw site.
|
|
41
|
+
*
|
|
42
|
+
* Always returns a promise, including for an operation that is entirely
|
|
43
|
+
* synchronous.
|
|
44
|
+
*
|
|
45
|
+
* @template T Type produced by the operation.
|
|
46
|
+
* @template E Type of the error captured on failure.
|
|
47
|
+
* @param operation Callback performing the operation, or a promise already
|
|
48
|
+
* running it.
|
|
49
|
+
* @returns The outcome of the operation.
|
|
50
|
+
*/
|
|
51
|
+
const tryCatch = async (operation) => {
|
|
52
|
+
try {
|
|
53
|
+
const data = await (typeof operation === 'function'
|
|
54
|
+
? operation()
|
|
55
|
+
: operation);
|
|
56
|
+
return { data, error: null };
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return { data: null, error: asStorableError(error) };
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
exports.tryCatch = tryCatch;
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fulcro/functions",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Runtime helpers turning control flow into values: an exhaustive switchFor and a throwing-free tryCatch.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"switch",
|
|
7
|
+
"exhaustive",
|
|
8
|
+
"enum",
|
|
9
|
+
"result",
|
|
10
|
+
"try-catch",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"author": "diguu <rodrigogeribola@hotmail.com>",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/DigUu-RL/fulcro.git",
|
|
37
|
+
"directory": "packages/functions"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/DigUu-RL/fulcro/tree/main/packages/functions#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/DigUu-RL/fulcro/issues"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
45
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
}
|
|
48
|
+
}
|