@pvorona/failable 0.0.1 → 0.0.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/README.md +283 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A typed result type for expected failures. `Failable<T, E>` is a discriminated union of `Success<T>` and `Failure<E>`, with ergonomic accessors and structured-clone support.
|
|
4
4
|
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @pvorona/failable
|
|
9
|
+
```
|
|
10
|
+
|
|
5
11
|
## Usage
|
|
6
12
|
|
|
7
13
|
```ts
|
|
@@ -19,6 +25,8 @@ if (result.isSuccess) {
|
|
|
19
25
|
### Factories
|
|
20
26
|
|
|
21
27
|
```ts
|
|
28
|
+
import { Failable } from '@pvorona/failable';
|
|
29
|
+
|
|
22
30
|
const success = Failable.ofSuccess(42);
|
|
23
31
|
const failure = Failable.ofError(new Error('boom'));
|
|
24
32
|
```
|
|
@@ -26,6 +34,10 @@ const failure = Failable.ofError(new Error('boom'));
|
|
|
26
34
|
### Fallbacks
|
|
27
35
|
|
|
28
36
|
```ts
|
|
37
|
+
import { Failable } from '@pvorona/failable';
|
|
38
|
+
|
|
39
|
+
const failure = Failable.ofError(new Error('boom'));
|
|
40
|
+
|
|
29
41
|
const value = failure.getOr('default'); // 'default'
|
|
30
42
|
const recovered = failure.or('fallback'); // Success<'fallback'>
|
|
31
43
|
```
|
|
@@ -33,6 +45,8 @@ const recovered = failure.or('fallback'); // Success<'fallback'>
|
|
|
33
45
|
### Wrapping async work
|
|
34
46
|
|
|
35
47
|
```ts
|
|
48
|
+
import { Failable } from '@pvorona/failable';
|
|
49
|
+
|
|
36
50
|
const result = await Failable.from(fetch('/api'));
|
|
37
51
|
```
|
|
38
52
|
|
|
@@ -41,6 +55,8 @@ const result = await Failable.from(fetch('/api'));
|
|
|
41
55
|
`Failable` instances use Symbols and prototype methods that do not survive structured cloning (`postMessage`, `chrome.runtime.sendMessage`, etc.). Convert to a plain object first:
|
|
42
56
|
|
|
43
57
|
```ts
|
|
58
|
+
import { Failable } from '@pvorona/failable';
|
|
59
|
+
|
|
44
60
|
// sender
|
|
45
61
|
const wire = Failable.toFailableLike(result);
|
|
46
62
|
postMessage(wire);
|
|
@@ -48,3 +64,270 @@ postMessage(wire);
|
|
|
48
64
|
// receiver
|
|
49
65
|
const hydrated = Failable.from(wire);
|
|
50
66
|
```
|
|
67
|
+
|
|
68
|
+
## API
|
|
69
|
+
|
|
70
|
+
### `const enum FailableStatus`
|
|
71
|
+
|
|
72
|
+
The discriminant for `FailableLike` and `Failable` instances.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
export const enum FailableStatus {
|
|
76
|
+
Success = 'success',
|
|
77
|
+
Failure = 'failure',
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { FailableStatus, type FailableLike } from '@pvorona/failable';
|
|
85
|
+
|
|
86
|
+
const ok: FailableLike<number, string> = {
|
|
87
|
+
status: FailableStatus.Success,
|
|
88
|
+
data: 1,
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `type Failable<T, E>`
|
|
93
|
+
|
|
94
|
+
Alias for `Success<T> | Failure<E>`.
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import type { Failable } from '@pvorona/failable';
|
|
100
|
+
import { Failable as FailableNS } from '@pvorona/failable';
|
|
101
|
+
|
|
102
|
+
export function parseIntSafe(input: string): Failable<number, Error> {
|
|
103
|
+
const n = Number(input);
|
|
104
|
+
if (!Number.isInteger(n)) return FailableNS.ofError(new Error('Not an int'));
|
|
105
|
+
|
|
106
|
+
return FailableNS.ofSuccess(n);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `type Success<T>`
|
|
111
|
+
|
|
112
|
+
The success variant.
|
|
113
|
+
|
|
114
|
+
Key fields/methods:
|
|
115
|
+
|
|
116
|
+
- `status: 'success'`, `isSuccess: true`, `isError: false`
|
|
117
|
+
- `data: T`, `error: null`
|
|
118
|
+
- `or(value)` returns itself
|
|
119
|
+
- `getOr(_)` returns `data`
|
|
120
|
+
- `getOrThrow()` returns `data`
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { Failable } from '@pvorona/failable';
|
|
126
|
+
|
|
127
|
+
const s = Failable.ofSuccess(123);
|
|
128
|
+
s.getOr(0); // 123
|
|
129
|
+
s.or('x'); // Success<number>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `type Failure<E>`
|
|
133
|
+
|
|
134
|
+
The failure variant.
|
|
135
|
+
|
|
136
|
+
Key fields/methods:
|
|
137
|
+
|
|
138
|
+
- `status: 'failure'`, `isSuccess: false`, `isError: true`
|
|
139
|
+
- `error: E`, `data: null`
|
|
140
|
+
- `or(value)` converts to `Success<typeof value>`
|
|
141
|
+
- `getOr(fallback)` returns `fallback`
|
|
142
|
+
- `getOrThrow()` throws `error`
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { Failable } from '@pvorona/failable';
|
|
148
|
+
|
|
149
|
+
const f = Failable.ofError(new Error('boom'));
|
|
150
|
+
f.getOr('default'); // 'default'
|
|
151
|
+
f.or(42).data; // 42
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `type FailableLike<T, E>`
|
|
155
|
+
|
|
156
|
+
Structured-clone-friendly representation of a result:
|
|
157
|
+
|
|
158
|
+
- `{ status: 'success', data }`
|
|
159
|
+
- `{ status: 'failure', error }`
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import type { FailableLike } from '@pvorona/failable';
|
|
165
|
+
|
|
166
|
+
type Wire = FailableLike<{ id: string }, { code: string }>;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `type FailableLikeSuccess<T>`
|
|
170
|
+
|
|
171
|
+
The success-shaped `FailableLike`.
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { FailableStatus, type FailableLikeSuccess } from '@pvorona/failable';
|
|
177
|
+
|
|
178
|
+
const wireOk: FailableLikeSuccess<number> = {
|
|
179
|
+
status: FailableStatus.Success,
|
|
180
|
+
data: 1,
|
|
181
|
+
};
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### `type FailableLikeFailure<E>`
|
|
185
|
+
|
|
186
|
+
The failure-shaped `FailableLike`.
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { FailableStatus, type FailableLikeFailure } from '@pvorona/failable';
|
|
192
|
+
|
|
193
|
+
const wireErr: FailableLikeFailure<string> = {
|
|
194
|
+
status: FailableStatus.Failure,
|
|
195
|
+
error: 'bad_request',
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `FailableTag`, `SuccessTag`, `FailureTag` (Symbols)
|
|
200
|
+
|
|
201
|
+
Low-level Symbol tags used to mark hydrated `Failable` instances at runtime.
|
|
202
|
+
|
|
203
|
+
Most code should prefer `result.isSuccess` / `result.isError` or the guards `Failable.isSuccess(...)` / `Failable.isFailure(...)`.
|
|
204
|
+
|
|
205
|
+
Example (advanced):
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { FailableTag } from '@pvorona/failable';
|
|
209
|
+
|
|
210
|
+
export function isHydratedFailable(value: unknown): boolean {
|
|
211
|
+
return (
|
|
212
|
+
typeof value === 'object' &&
|
|
213
|
+
value !== null &&
|
|
214
|
+
(value as any)[FailableTag] === true
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### `const Failable`
|
|
220
|
+
|
|
221
|
+
Namespace-style factory + utilities for producing and working with `Failable<T, E>`.
|
|
222
|
+
|
|
223
|
+
#### `Failable.ofSuccess<T>(data: T): Success<T>`
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { Failable } from '@pvorona/failable';
|
|
229
|
+
|
|
230
|
+
const ok = Failable.ofSuccess({ id: '1' });
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `Failable.ofError<E>(error: E): Failure<E>`
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { Failable } from '@pvorona/failable';
|
|
239
|
+
|
|
240
|
+
const err = Failable.ofError({ code: 'bad_request' });
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### `Failable.isFailable(value): value is Failable<unknown, unknown>`
|
|
244
|
+
|
|
245
|
+
Checks whether a value is a hydrated `Failable` instance (Symbol-tagged).
|
|
246
|
+
|
|
247
|
+
Example:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { Failable } from '@pvorona/failable';
|
|
251
|
+
|
|
252
|
+
const maybe: unknown = Failable.ofSuccess(1);
|
|
253
|
+
if (Failable.isFailable(maybe)) {
|
|
254
|
+
// narrowed
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### `Failable.isSuccess(value): value is Success<unknown>`
|
|
259
|
+
|
|
260
|
+
Example:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { Failable } from '@pvorona/failable';
|
|
264
|
+
|
|
265
|
+
const maybe: unknown = Failable.ofSuccess(1);
|
|
266
|
+
if (Failable.isSuccess(maybe)) {
|
|
267
|
+
maybe.data; // ok
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### `Failable.isFailure(value): value is Failure<unknown>`
|
|
272
|
+
|
|
273
|
+
Example:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { Failable } from '@pvorona/failable';
|
|
277
|
+
|
|
278
|
+
const maybe: unknown = Failable.ofError('nope');
|
|
279
|
+
if (Failable.isFailure(maybe)) {
|
|
280
|
+
console.log(maybe.error);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### `Failable.toFailableLike(result): FailableLike<...>`
|
|
285
|
+
|
|
286
|
+
Converts a hydrated `Failable` into a structured-clone-friendly representation.
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import { Failable } from '@pvorona/failable';
|
|
292
|
+
|
|
293
|
+
const res = Failable.ofSuccess(1);
|
|
294
|
+
const wire = Failable.toFailableLike(res);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### `Failable.isFailableLike(value): value is FailableLike<unknown, unknown>`
|
|
298
|
+
|
|
299
|
+
Strictly checks for `{ status, data }` or `{ status, error }` with no extra enumerable keys.
|
|
300
|
+
|
|
301
|
+
Example:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
import { Failable } from '@pvorona/failable';
|
|
305
|
+
|
|
306
|
+
const wire: unknown = { status: 'success', data: 1 };
|
|
307
|
+
Failable.isFailableLike(wire); // true
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### `Failable.from(...)`
|
|
311
|
+
|
|
312
|
+
Overloads:
|
|
313
|
+
|
|
314
|
+
- `from(failable)` → returns the same instance
|
|
315
|
+
- `from(failableLike)` → rehydrates into a real `Success` / `Failure`
|
|
316
|
+
- `from(() => value)` → captures throws into `Failure`
|
|
317
|
+
- `from(promise)` → captures rejections into `Failure`
|
|
318
|
+
|
|
319
|
+
Examples:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { Failable } from '@pvorona/failable';
|
|
323
|
+
|
|
324
|
+
// function wrapper (captures throws)
|
|
325
|
+
const res1 = Failable.from(() => JSON.parse('{'));
|
|
326
|
+
|
|
327
|
+
// promise wrapper (captures rejections)
|
|
328
|
+
const res2 = await Failable.from(fetch('https://example.com'));
|
|
329
|
+
|
|
330
|
+
// rehydrate from structured clone
|
|
331
|
+
const wire = Failable.toFailableLike(Failable.ofError('bad'));
|
|
332
|
+
const hydrated = Failable.from(wire);
|
|
333
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pvorona/failable",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"module": "./dist/index.js",
|
|
@@ -18,8 +19,11 @@
|
|
|
18
19
|
"dist",
|
|
19
20
|
"!**/*.tsbuildinfo"
|
|
20
21
|
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
21
25
|
"dependencies": {
|
|
22
|
-
"@pvorona/assert": "
|
|
23
|
-
"@pvorona/not-implemented": "
|
|
26
|
+
"@pvorona/assert": "~0.0.2",
|
|
27
|
+
"@pvorona/not-implemented": "~0.0.1"
|
|
24
28
|
}
|
|
25
29
|
}
|