@crbroughton/failsafe 0.2.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/README.md +212 -0
- package/dist/pipe/index.d.mts +38 -0
- package/dist/pipe/index.d.ts +38 -0
- package/dist/pipe/index.mjs +5 -0
- package/dist/safe/index.d.mts +282 -0
- package/dist/safe/index.d.ts +282 -0
- package/dist/safe/index.mjs +35 -0
- package/dist/try/index.d.mts +166 -0
- package/dist/try/index.d.ts +166 -0
- package/dist/try/index.mjs +64 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# @crbroughton/failsafe
|
|
2
|
+
|
|
3
|
+
Type-safe error handling utilities for TypeScript. A `Result<T, E>` type
|
|
4
|
+
(inspired by Rust) plus helpers for turning throwing browser/Node APIs into
|
|
5
|
+
values instead of exceptions.
|
|
6
|
+
|
|
7
|
+
Lightweight, dependency-free alternative to neverthrow, plus additional
|
|
8
|
+
helpers for wrapping browser/Node APIs that throw exceptions.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pnpm add @crbroughton/failsafe
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { chain, isOk, match, matchResult, type TaggedError } from "@crbroughton/failsafe"
|
|
20
|
+
import { tryJSONParse, tryLocalStorageGet } from "@crbroughton/failsafe/try"
|
|
21
|
+
|
|
22
|
+
interface User { name: string }
|
|
23
|
+
|
|
24
|
+
const result = chain(
|
|
25
|
+
tryLocalStorageGet("user"),
|
|
26
|
+
raw => raw === null
|
|
27
|
+
? { ok: false as const, error: { tag: "JSONParseError" as const, raw: "" } }
|
|
28
|
+
: tryJSONParse<User>(raw),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if (isOk(result)) {
|
|
32
|
+
console.log(result.value.name)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const message = matchResult(result, {
|
|
36
|
+
ok: user => `Welcome, ${user.name}`,
|
|
37
|
+
err: error => `Could not load user: ${error.tag}`,
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Exports
|
|
42
|
+
|
|
43
|
+
- `@crbroughton/failsafe`: the `Result` type, `Ok`/`Err`, guards, and matchers
|
|
44
|
+
- `@crbroughton/failsafe/try`: `Result`-returning wrappers around throwing web/Node APIs
|
|
45
|
+
- `@crbroughton/failsafe/pipe`: plain left-to-right function composition
|
|
46
|
+
|
|
47
|
+
## `@crbroughton/failsafe`
|
|
48
|
+
|
|
49
|
+
### `Result<T, E = Error>`
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
type Result<T, E = Error> = Ok<T> | Err<E>
|
|
53
|
+
interface Ok<T> { ok: true, value: T }
|
|
54
|
+
interface Err<E> { ok: false, error: E }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `Ok(value)` / `Err(error)`
|
|
58
|
+
|
|
59
|
+
Construct a `Result` directly.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const success = Ok(42) // { ok: true, value: 42 }
|
|
63
|
+
const failure = Err("boom") // { ok: false, error: "boom" }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `isOk(result)` / `isErr(result)`
|
|
67
|
+
|
|
68
|
+
Type guards that narrow a `Result` to `Ok<T>` or `Err<E>`.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
if (isOk(result)) {
|
|
72
|
+
result.value // T
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `ok(result)` / `err(result)`
|
|
77
|
+
|
|
78
|
+
Extract the value or error as a nullable, for quick one-liners.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const value = ok(result) // T | null
|
|
82
|
+
const error = err(result) // E | null
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `safe(fn | promise, mapError?)`
|
|
86
|
+
|
|
87
|
+
Runs a throwing sync function, or awaits a promise, and returns a `Result`
|
|
88
|
+
instead of throwing/rejecting. Optionally maps the caught value into a typed
|
|
89
|
+
error.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// Sync: pass a thunk
|
|
93
|
+
const parsed = safe(() => JSON.parse(raw)) // Result<any, Error>
|
|
94
|
+
|
|
95
|
+
// Async: pass the Promise directly (not a function that returns one)
|
|
96
|
+
const fetched = await safe(fetch("/api/data")) // Result<Response, Error>
|
|
97
|
+
|
|
98
|
+
// With a typed error mapper
|
|
99
|
+
type FetchError = TaggedError<"FetchError", { url: string }>
|
|
100
|
+
const tagged = await safe(
|
|
101
|
+
fetchData(),
|
|
102
|
+
(): FetchError => ({ tag: "FetchError", url: "/api/data" }),
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> Passing an async function itself (`safe(async () => ...)`) matches the sync
|
|
107
|
+
> overload, since calling it never throws synchronously; it just returns a
|
|
108
|
+
> Promise. Always pass the awaited Promise directly for async code.
|
|
109
|
+
|
|
110
|
+
### `chain(result, fn)`
|
|
111
|
+
|
|
112
|
+
Chains a `Result`-returning function onto an existing `Result`,
|
|
113
|
+
short-circuiting on `Err`.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const result = chain(tryLocalStorageGet("user"), raw =>
|
|
117
|
+
raw === null ? Err({ tag: "ParseError", raw: "" }) : tryJSONParse<User>(raw))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `TaggedError<Tag, Extra?>`
|
|
121
|
+
|
|
122
|
+
A discriminated error shape: every error carries a `tag` so consumers can
|
|
123
|
+
narrow on it.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
type ParseError = TaggedError<"ParseError", { raw: string }>
|
|
127
|
+
type StorageError = TaggedError<"StorageError", { key: string }>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `matchResult(result, { ok, err })`
|
|
131
|
+
|
|
132
|
+
Exhaustively matches a `Result`, requiring both handlers.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const message = matchResult(result, {
|
|
136
|
+
ok: value => `Got ${value}`,
|
|
137
|
+
err: error => `Failed: ${error.message}`,
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `match(taggedError, handlers)`
|
|
142
|
+
|
|
143
|
+
Exhaustively matches a tagged error union. TypeScript requires a handler for
|
|
144
|
+
every tag, and adding a new tag later forces every call site to update.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
match(result.error, {
|
|
148
|
+
ParseError: e => console.error("bad json:", e.raw),
|
|
149
|
+
StorageError: e => console.error("storage failed:", e.key),
|
|
150
|
+
// omitting a tag here is a compile error
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## `@crbroughton/failsafe/try`
|
|
155
|
+
|
|
156
|
+
`Result`-returning wrappers around browser/Node APIs that throw. Each
|
|
157
|
+
mirrors its native function's signature exactly (including overloaded
|
|
158
|
+
argument types) and returns a `TaggedError` on failure.
|
|
159
|
+
|
|
160
|
+
| Function | Wraps | Error tag |
|
|
161
|
+
| --- | --- | --- |
|
|
162
|
+
| `tryJSONParse<T>(raw, reviver?)` | `JSON.parse` | `JSONParseError` |
|
|
163
|
+
| `tryJSONStringify(value, replacer?, space?)` | `JSON.stringify` | `JSONStringifyError` |
|
|
164
|
+
| `tryURIDecode(input)` | `decodeURIComponent` | `URIError` |
|
|
165
|
+
| `tryURIEncode(input)` | `encodeURIComponent` | `URIError` |
|
|
166
|
+
| `tryBase64Decode(input)` | `atob` | `Base64Error` |
|
|
167
|
+
| `tryBase64Encode(input)` | `btoa` | `Base64Error` |
|
|
168
|
+
| `tryLocalStorageGet(key)` | `localStorage.getItem` | `StorageError` |
|
|
169
|
+
| `tryLocalStorageSet(key, value)` | `localStorage.setItem` | `StorageError` |
|
|
170
|
+
| `tryURL(input, base?)` | `new URL(...)` | `URLParseError` |
|
|
171
|
+
| `tryStructuredClone<T>(value, options?)` | `structuredClone` | `StructuredCloneError` |
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { tryJSONParse, tryURL } from "@crbroughton/failsafe/try"
|
|
175
|
+
|
|
176
|
+
const parsed = tryJSONParse<User>(raw)
|
|
177
|
+
const url = tryURL(userSuppliedString)
|
|
178
|
+
|
|
179
|
+
if (isOk(url)) {
|
|
180
|
+
console.log(url.value.hostname)
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## `@crbroughton/failsafe/pipe`
|
|
185
|
+
|
|
186
|
+
### `pipe(value, ...fns)`
|
|
187
|
+
|
|
188
|
+
Pipes a value through up to five functions, left to right. Deliberately
|
|
189
|
+
independent of `Result`; plain function composition. Compose it with `chain`
|
|
190
|
+
for a `Result` pipeline.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { pipe } from "@crbroughton/failsafe/pipe"
|
|
194
|
+
|
|
195
|
+
const slug = pipe(
|
|
196
|
+
" Hello World ",
|
|
197
|
+
s => s.trim(),
|
|
198
|
+
s => s.toLowerCase(),
|
|
199
|
+
s => s.replace(/\s+/g, "-"),
|
|
200
|
+
)
|
|
201
|
+
// 'hello-world'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
|
|
206
|
+
This repo uses [devenv](https://devenv.sh) to provide Node and pnpm.
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
devenv shell
|
|
210
|
+
pnpm install
|
|
211
|
+
pnpm exec nx run-many -t build test lint typecheck
|
|
212
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipes a value through a sequence of functions, left to right — each
|
|
3
|
+
* function's output becomes the next function's input.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately independent of Result/safe: this is plain function
|
|
6
|
+
* composition and knows nothing about Ok/Err or short-circuiting. For a
|
|
7
|
+
* pipeline that stops early on an Err, use `chain` from the package root
|
|
8
|
+
* instead — the two compose fine together (see example below).
|
|
9
|
+
*
|
|
10
|
+
* @param value The initial value
|
|
11
|
+
* @returns The result of applying every function in sequence
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const slug = pipe(' Hello World ', (s) => s.trim(), (s) => s.toLowerCase(), (s) => s.replace(/\s+/g, '-'))
|
|
16
|
+
* // 'hello-world'
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* // Composing with chain (from '@crbroughton/failsafe') for a Result pipeline:
|
|
22
|
+
* import { chain, Err } from '@crbroughton/failsafe'
|
|
23
|
+
* import { tryLocalStorageGet, tryJSONParse } from '@crbroughton/failsafe/try'
|
|
24
|
+
*
|
|
25
|
+
* const result = pipe(
|
|
26
|
+
* tryLocalStorageGet('user'),
|
|
27
|
+
* (r) => chain(r, (raw) => raw === null ? Err({ tag: 'JSONParseError', raw: '' }) : tryJSONParse<User>(raw)),
|
|
28
|
+
* )
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
declare function pipe<T>(value: T): T;
|
|
32
|
+
declare function pipe<T, A>(value: T, fn1: (v: T) => A): A;
|
|
33
|
+
declare function pipe<T, A, B>(value: T, fn1: (v: T) => A, fn2: (v: A) => B): B;
|
|
34
|
+
declare function pipe<T, A, B, C>(value: T, fn1: (v: T) => A, fn2: (v: A) => B, fn3: (v: B) => C): C;
|
|
35
|
+
declare function pipe<T, A, B, C, D>(value: T, fn1: (v: T) => A, fn2: (v: A) => B, fn3: (v: B) => C, fn4: (v: C) => D): D;
|
|
36
|
+
declare function pipe<T, A, B, C, D, E>(value: T, fn1: (v: T) => A, fn2: (v: A) => B, fn3: (v: B) => C, fn4: (v: C) => D, fn5: (v: D) => E): E;
|
|
37
|
+
|
|
38
|
+
export { pipe };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipes a value through a sequence of functions, left to right — each
|
|
3
|
+
* function's output becomes the next function's input.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately independent of Result/safe: this is plain function
|
|
6
|
+
* composition and knows nothing about Ok/Err or short-circuiting. For a
|
|
7
|
+
* pipeline that stops early on an Err, use `chain` from the package root
|
|
8
|
+
* instead — the two compose fine together (see example below).
|
|
9
|
+
*
|
|
10
|
+
* @param value The initial value
|
|
11
|
+
* @returns The result of applying every function in sequence
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const slug = pipe(' Hello World ', (s) => s.trim(), (s) => s.toLowerCase(), (s) => s.replace(/\s+/g, '-'))
|
|
16
|
+
* // 'hello-world'
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* // Composing with chain (from '@crbroughton/failsafe') for a Result pipeline:
|
|
22
|
+
* import { chain, Err } from '@crbroughton/failsafe'
|
|
23
|
+
* import { tryLocalStorageGet, tryJSONParse } from '@crbroughton/failsafe/try'
|
|
24
|
+
*
|
|
25
|
+
* const result = pipe(
|
|
26
|
+
* tryLocalStorageGet('user'),
|
|
27
|
+
* (r) => chain(r, (raw) => raw === null ? Err({ tag: 'JSONParseError', raw: '' }) : tryJSONParse<User>(raw)),
|
|
28
|
+
* )
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
declare function pipe<T>(value: T): T;
|
|
32
|
+
declare function pipe<T, A>(value: T, fn1: (v: T) => A): A;
|
|
33
|
+
declare function pipe<T, A, B>(value: T, fn1: (v: T) => A, fn2: (v: A) => B): B;
|
|
34
|
+
declare function pipe<T, A, B, C>(value: T, fn1: (v: T) => A, fn2: (v: A) => B, fn3: (v: B) => C): C;
|
|
35
|
+
declare function pipe<T, A, B, C, D>(value: T, fn1: (v: T) => A, fn2: (v: A) => B, fn3: (v: B) => C, fn4: (v: C) => D): D;
|
|
36
|
+
declare function pipe<T, A, B, C, D, E>(value: T, fn1: (v: T) => A, fn2: (v: A) => B, fn3: (v: B) => C, fn4: (v: C) => D, fn5: (v: D) => E): E;
|
|
37
|
+
|
|
38
|
+
export { pipe };
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Result type representing either success (Ok) or failure (Err).
|
|
3
|
+
* Inspired by Rust's Result<T, E> type for explicit error handling.
|
|
4
|
+
*
|
|
5
|
+
* @template T The type of the success value
|
|
6
|
+
* @template E The type of the error (defaults to Error)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const result: Result<number, string> = Ok(42)
|
|
11
|
+
*
|
|
12
|
+
* if (result.ok === false) {
|
|
13
|
+
* console.error(result.error) // TypeScript knows this is string
|
|
14
|
+
* } else {
|
|
15
|
+
* console.log(result.value) // TypeScript knows this is number
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
type Result<T, E = Error> = Ok<T> | Err<E>;
|
|
20
|
+
/**
|
|
21
|
+
* Represents a successful result containing a value.
|
|
22
|
+
* @template T The type of the success value
|
|
23
|
+
*/
|
|
24
|
+
interface Ok<T> {
|
|
25
|
+
ok: true;
|
|
26
|
+
value: T;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a successful Result with the given value.
|
|
30
|
+
*
|
|
31
|
+
* @template T The type of the value
|
|
32
|
+
* @param value The success value to wrap
|
|
33
|
+
* @returns An Ok result containing the value
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const result = Ok(42)
|
|
38
|
+
* // result is { ok: true, value: 42 }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare const Ok: <T>(value: T) => Ok<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Represents a failed result containing an error.
|
|
44
|
+
* @template E The type of the error
|
|
45
|
+
*/
|
|
46
|
+
interface Err<E> {
|
|
47
|
+
ok: false;
|
|
48
|
+
error: E;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates a failed Result with the given error.
|
|
52
|
+
*
|
|
53
|
+
* @template E The type of the error
|
|
54
|
+
* @param error The error to wrap
|
|
55
|
+
* @returns An Err result containing the error
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const result = Err('Something went wrong')
|
|
60
|
+
* // result is { ok: false, error: 'Something went wrong' }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare const Err: <E>(error: E) => Err<E>;
|
|
64
|
+
/**
|
|
65
|
+
* Type guard to check if a Result is Ok.
|
|
66
|
+
* Narrows the type to Ok<T> when true.
|
|
67
|
+
*
|
|
68
|
+
* @template T The type of the success value
|
|
69
|
+
* @template E The type of the error
|
|
70
|
+
* @param result The Result to check
|
|
71
|
+
* @returns true if the result is Ok, false otherwise
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const result = safe(() => JSON.parse(raw))
|
|
76
|
+
*
|
|
77
|
+
* if (isOk(result)) {
|
|
78
|
+
* // TypeScript knows result is Ok<T>
|
|
79
|
+
* console.log(result.value)
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function isOk<T, E>(result: Result<T, E>): result is Ok<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Type guard to check if a Result is Err.
|
|
86
|
+
* Narrows the type to Err<E> when true.
|
|
87
|
+
*
|
|
88
|
+
* @template T The type of the success value
|
|
89
|
+
* @template E The type of the error
|
|
90
|
+
* @param result The Result to check
|
|
91
|
+
* @returns true if the result is Err, false otherwise
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const result = safe(() => JSON.parse(raw))
|
|
96
|
+
*
|
|
97
|
+
* if (isErr(result)) {
|
|
98
|
+
* // TypeScript knows result is Err<E>
|
|
99
|
+
* console.error(result.error)
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare function isErr<T, E>(result: Result<T, E>): result is Err<E>;
|
|
104
|
+
/**
|
|
105
|
+
* Extracts the value from a Result if Ok, otherwise returns null.
|
|
106
|
+
* Convenient for converting Result to nullable value.
|
|
107
|
+
*
|
|
108
|
+
* @template T The type of the success value
|
|
109
|
+
* @template E The type of the error
|
|
110
|
+
* @param result The Result to extract from
|
|
111
|
+
* @returns The value if Ok, null if Err
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const result = safe(() => JSON.parse(raw))
|
|
116
|
+
* const value = ok(result) // T | null
|
|
117
|
+
*
|
|
118
|
+
* const name = ok(result)?.name ?? 'Guest'
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
declare function ok<T, E>(result: Result<T, E>): T | null;
|
|
122
|
+
/**
|
|
123
|
+
* Extracts the error from a Result if Err, otherwise returns null.
|
|
124
|
+
* Convenient for converting Result to nullable error.
|
|
125
|
+
*
|
|
126
|
+
* @template T The type of the success value
|
|
127
|
+
* @template E The type of the error
|
|
128
|
+
* @param result The Result to extract from
|
|
129
|
+
* @returns The error if Err, null if Ok
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* const result = safe(() => JSON.parse(raw))
|
|
134
|
+
* const error = err(result) // E | null
|
|
135
|
+
*
|
|
136
|
+
* if (error) {
|
|
137
|
+
* console.error('Failed:', error)
|
|
138
|
+
* }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
declare function err<T, E>(result: Result<T, E>): E | null;
|
|
142
|
+
/**
|
|
143
|
+
* Runs a throwing SYNCHRONOUS function and returns a Result instead of throwing.
|
|
144
|
+
* Optionally maps the caught error into a typed shape.
|
|
145
|
+
*
|
|
146
|
+
* @template T The return type of the function if it succeeds
|
|
147
|
+
* @template E The error type (defaults to Error)
|
|
148
|
+
* @param fn The synchronous function to run
|
|
149
|
+
* @param mapError Optional — transform the caught value into a typed error
|
|
150
|
+
* @returns Ok(value) or Err(mappedError)
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* const result = safe(() => JSON.parse(raw))
|
|
155
|
+
* // Result<any, Error>
|
|
156
|
+
*
|
|
157
|
+
* type ParseError = TaggedError<'ParseError', { raw: string }>
|
|
158
|
+
*
|
|
159
|
+
* const tagged = safe(
|
|
160
|
+
* () => JSON.parse(raw) as User,
|
|
161
|
+
* (): ParseError => ({ tag: 'ParseError', raw })
|
|
162
|
+
* )
|
|
163
|
+
* // Result<User, ParseError>
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function safe<T, E = Error>(fn: () => T, mapError?: (error: unknown) => E): Result<T, E>;
|
|
167
|
+
/**
|
|
168
|
+
* Awaits a Promise and returns a Result instead of letting it reject.
|
|
169
|
+
* Optionally maps the caught error into a typed shape.
|
|
170
|
+
*
|
|
171
|
+
* @template T The type of the resolved promise value
|
|
172
|
+
* @template E The error type (defaults to Error)
|
|
173
|
+
* @param promise The promise to await
|
|
174
|
+
* @param mapError Optional — transform the caught value into a typed error
|
|
175
|
+
* @returns A Promise that resolves to Ok(value) or Err(mappedError)
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* const result = await safe(fetch('/api/data'))
|
|
180
|
+
* if (result.ok === false) {
|
|
181
|
+
* console.error('Fetch failed:', result.error)
|
|
182
|
+
* return
|
|
183
|
+
* }
|
|
184
|
+
* const response = result.value
|
|
185
|
+
*
|
|
186
|
+
* type FetchError = TaggedError<'FetchError', { url: string }>
|
|
187
|
+
*
|
|
188
|
+
* const tagged = await safe(
|
|
189
|
+
* fetchData(),
|
|
190
|
+
* (): FetchError => ({ tag: 'FetchError', url })
|
|
191
|
+
* )
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
declare function safe<T, E = Error>(promise: Promise<T>, mapError?: (error: unknown) => E): Promise<Result<T, E>>;
|
|
195
|
+
/**
|
|
196
|
+
* Chains a Result-returning function onto an existing Result,
|
|
197
|
+
* short-circuiting if the input is already an Err.
|
|
198
|
+
*
|
|
199
|
+
* @template T The input success type
|
|
200
|
+
* @template U The output success type
|
|
201
|
+
* @template E The error type (shared between input and output)
|
|
202
|
+
* @param result The Result to chain from
|
|
203
|
+
* @param fn A function that takes the unwrapped Ok value and returns a new Result
|
|
204
|
+
* @returns The result of fn if input was Ok, otherwise the original Err unchanged
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* const result = chain(tryLocalStorageGet('user'), (raw) =>
|
|
209
|
+
* raw === null ? Err({ tag: 'ParseError', raw: '' }) : tryJSONParse<User>(raw))
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
declare function chain<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
|
|
213
|
+
/**
|
|
214
|
+
* A discriminated error shape — every error carries a `tag` so consumers
|
|
215
|
+
* can narrow on it (if/switch, or match for exhaustive handling).
|
|
216
|
+
*
|
|
217
|
+
* @template Tag The string literal discriminant
|
|
218
|
+
* @template Extra Additional fields specific to this error
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* type ParseError = TaggedError<'ParseError', { raw: string }>
|
|
223
|
+
* type StorageError = TaggedError<'StorageError', { key: string }>
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
type TaggedError<Tag extends string, Extra extends object = object> = {
|
|
227
|
+
tag: Tag;
|
|
228
|
+
} & Extra;
|
|
229
|
+
/**
|
|
230
|
+
* Exhaustively matches a Result — requires both an ok and an err handler.
|
|
231
|
+
*
|
|
232
|
+
* @template T The success type
|
|
233
|
+
* @template E The error type
|
|
234
|
+
* @template R The return type of both handlers
|
|
235
|
+
* @param result The Result to match on
|
|
236
|
+
* @param handlers Handlers for the ok and err cases
|
|
237
|
+
* @param handlers.ok Called with the unwrapped value when result is Ok
|
|
238
|
+
* @param handlers.err Called with the unwrapped error when result is Err
|
|
239
|
+
* @returns Whichever handler's return value applies
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* const message = matchResult(result, {
|
|
244
|
+
* ok: (value) => `Got ${value}`,
|
|
245
|
+
* err: (error) => `Failed: ${error.message}`,
|
|
246
|
+
* })
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
declare function matchResult<T, E, R>(result: Result<T, E>, handlers: {
|
|
250
|
+
ok: (value: T) => R;
|
|
251
|
+
err: (error: E) => R;
|
|
252
|
+
}): R;
|
|
253
|
+
/**
|
|
254
|
+
* Exhaustively matches a tagged error union. TypeScript requires a handler
|
|
255
|
+
* for every tag in the union — omitting one is a compile error, and adding
|
|
256
|
+
* a new tag later forces every call site to be updated.
|
|
257
|
+
*
|
|
258
|
+
* @template E The tagged error union type
|
|
259
|
+
* @template Handlers An object with one handler per tag in E
|
|
260
|
+
* @param error The tagged error to match on
|
|
261
|
+
* @param handlers An object mapping each tag to a handler function
|
|
262
|
+
* @returns Whichever handler's return value applies
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* match(result.error, {
|
|
267
|
+
* ParseError: (e) => console.error('bad json:', e.raw),
|
|
268
|
+
* StorageError: (e) => console.error('storage failed:', e.key),
|
|
269
|
+
* // omitting a tag here is a compile error
|
|
270
|
+
* })
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
declare function match<E extends {
|
|
274
|
+
tag: string;
|
|
275
|
+
}, Handlers extends {
|
|
276
|
+
[K in E["tag"]]: (error: Extract<E, {
|
|
277
|
+
tag: K;
|
|
278
|
+
}>) => unknown;
|
|
279
|
+
}>(error: E, handlers: Handlers): ReturnType<Handlers[E["tag"]]>;
|
|
280
|
+
|
|
281
|
+
export { Err, Ok, chain, err, isErr, isOk, match, matchResult, ok, safe };
|
|
282
|
+
export type { Result, TaggedError };
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Result type representing either success (Ok) or failure (Err).
|
|
3
|
+
* Inspired by Rust's Result<T, E> type for explicit error handling.
|
|
4
|
+
*
|
|
5
|
+
* @template T The type of the success value
|
|
6
|
+
* @template E The type of the error (defaults to Error)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const result: Result<number, string> = Ok(42)
|
|
11
|
+
*
|
|
12
|
+
* if (result.ok === false) {
|
|
13
|
+
* console.error(result.error) // TypeScript knows this is string
|
|
14
|
+
* } else {
|
|
15
|
+
* console.log(result.value) // TypeScript knows this is number
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
type Result<T, E = Error> = Ok<T> | Err<E>;
|
|
20
|
+
/**
|
|
21
|
+
* Represents a successful result containing a value.
|
|
22
|
+
* @template T The type of the success value
|
|
23
|
+
*/
|
|
24
|
+
interface Ok<T> {
|
|
25
|
+
ok: true;
|
|
26
|
+
value: T;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a successful Result with the given value.
|
|
30
|
+
*
|
|
31
|
+
* @template T The type of the value
|
|
32
|
+
* @param value The success value to wrap
|
|
33
|
+
* @returns An Ok result containing the value
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const result = Ok(42)
|
|
38
|
+
* // result is { ok: true, value: 42 }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare const Ok: <T>(value: T) => Ok<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Represents a failed result containing an error.
|
|
44
|
+
* @template E The type of the error
|
|
45
|
+
*/
|
|
46
|
+
interface Err<E> {
|
|
47
|
+
ok: false;
|
|
48
|
+
error: E;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates a failed Result with the given error.
|
|
52
|
+
*
|
|
53
|
+
* @template E The type of the error
|
|
54
|
+
* @param error The error to wrap
|
|
55
|
+
* @returns An Err result containing the error
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const result = Err('Something went wrong')
|
|
60
|
+
* // result is { ok: false, error: 'Something went wrong' }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare const Err: <E>(error: E) => Err<E>;
|
|
64
|
+
/**
|
|
65
|
+
* Type guard to check if a Result is Ok.
|
|
66
|
+
* Narrows the type to Ok<T> when true.
|
|
67
|
+
*
|
|
68
|
+
* @template T The type of the success value
|
|
69
|
+
* @template E The type of the error
|
|
70
|
+
* @param result The Result to check
|
|
71
|
+
* @returns true if the result is Ok, false otherwise
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const result = safe(() => JSON.parse(raw))
|
|
76
|
+
*
|
|
77
|
+
* if (isOk(result)) {
|
|
78
|
+
* // TypeScript knows result is Ok<T>
|
|
79
|
+
* console.log(result.value)
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function isOk<T, E>(result: Result<T, E>): result is Ok<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Type guard to check if a Result is Err.
|
|
86
|
+
* Narrows the type to Err<E> when true.
|
|
87
|
+
*
|
|
88
|
+
* @template T The type of the success value
|
|
89
|
+
* @template E The type of the error
|
|
90
|
+
* @param result The Result to check
|
|
91
|
+
* @returns true if the result is Err, false otherwise
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const result = safe(() => JSON.parse(raw))
|
|
96
|
+
*
|
|
97
|
+
* if (isErr(result)) {
|
|
98
|
+
* // TypeScript knows result is Err<E>
|
|
99
|
+
* console.error(result.error)
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare function isErr<T, E>(result: Result<T, E>): result is Err<E>;
|
|
104
|
+
/**
|
|
105
|
+
* Extracts the value from a Result if Ok, otherwise returns null.
|
|
106
|
+
* Convenient for converting Result to nullable value.
|
|
107
|
+
*
|
|
108
|
+
* @template T The type of the success value
|
|
109
|
+
* @template E The type of the error
|
|
110
|
+
* @param result The Result to extract from
|
|
111
|
+
* @returns The value if Ok, null if Err
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const result = safe(() => JSON.parse(raw))
|
|
116
|
+
* const value = ok(result) // T | null
|
|
117
|
+
*
|
|
118
|
+
* const name = ok(result)?.name ?? 'Guest'
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
declare function ok<T, E>(result: Result<T, E>): T | null;
|
|
122
|
+
/**
|
|
123
|
+
* Extracts the error from a Result if Err, otherwise returns null.
|
|
124
|
+
* Convenient for converting Result to nullable error.
|
|
125
|
+
*
|
|
126
|
+
* @template T The type of the success value
|
|
127
|
+
* @template E The type of the error
|
|
128
|
+
* @param result The Result to extract from
|
|
129
|
+
* @returns The error if Err, null if Ok
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* const result = safe(() => JSON.parse(raw))
|
|
134
|
+
* const error = err(result) // E | null
|
|
135
|
+
*
|
|
136
|
+
* if (error) {
|
|
137
|
+
* console.error('Failed:', error)
|
|
138
|
+
* }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
declare function err<T, E>(result: Result<T, E>): E | null;
|
|
142
|
+
/**
|
|
143
|
+
* Runs a throwing SYNCHRONOUS function and returns a Result instead of throwing.
|
|
144
|
+
* Optionally maps the caught error into a typed shape.
|
|
145
|
+
*
|
|
146
|
+
* @template T The return type of the function if it succeeds
|
|
147
|
+
* @template E The error type (defaults to Error)
|
|
148
|
+
* @param fn The synchronous function to run
|
|
149
|
+
* @param mapError Optional — transform the caught value into a typed error
|
|
150
|
+
* @returns Ok(value) or Err(mappedError)
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* const result = safe(() => JSON.parse(raw))
|
|
155
|
+
* // Result<any, Error>
|
|
156
|
+
*
|
|
157
|
+
* type ParseError = TaggedError<'ParseError', { raw: string }>
|
|
158
|
+
*
|
|
159
|
+
* const tagged = safe(
|
|
160
|
+
* () => JSON.parse(raw) as User,
|
|
161
|
+
* (): ParseError => ({ tag: 'ParseError', raw })
|
|
162
|
+
* )
|
|
163
|
+
* // Result<User, ParseError>
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function safe<T, E = Error>(fn: () => T, mapError?: (error: unknown) => E): Result<T, E>;
|
|
167
|
+
/**
|
|
168
|
+
* Awaits a Promise and returns a Result instead of letting it reject.
|
|
169
|
+
* Optionally maps the caught error into a typed shape.
|
|
170
|
+
*
|
|
171
|
+
* @template T The type of the resolved promise value
|
|
172
|
+
* @template E The error type (defaults to Error)
|
|
173
|
+
* @param promise The promise to await
|
|
174
|
+
* @param mapError Optional — transform the caught value into a typed error
|
|
175
|
+
* @returns A Promise that resolves to Ok(value) or Err(mappedError)
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* const result = await safe(fetch('/api/data'))
|
|
180
|
+
* if (result.ok === false) {
|
|
181
|
+
* console.error('Fetch failed:', result.error)
|
|
182
|
+
* return
|
|
183
|
+
* }
|
|
184
|
+
* const response = result.value
|
|
185
|
+
*
|
|
186
|
+
* type FetchError = TaggedError<'FetchError', { url: string }>
|
|
187
|
+
*
|
|
188
|
+
* const tagged = await safe(
|
|
189
|
+
* fetchData(),
|
|
190
|
+
* (): FetchError => ({ tag: 'FetchError', url })
|
|
191
|
+
* )
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
declare function safe<T, E = Error>(promise: Promise<T>, mapError?: (error: unknown) => E): Promise<Result<T, E>>;
|
|
195
|
+
/**
|
|
196
|
+
* Chains a Result-returning function onto an existing Result,
|
|
197
|
+
* short-circuiting if the input is already an Err.
|
|
198
|
+
*
|
|
199
|
+
* @template T The input success type
|
|
200
|
+
* @template U The output success type
|
|
201
|
+
* @template E The error type (shared between input and output)
|
|
202
|
+
* @param result The Result to chain from
|
|
203
|
+
* @param fn A function that takes the unwrapped Ok value and returns a new Result
|
|
204
|
+
* @returns The result of fn if input was Ok, otherwise the original Err unchanged
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* const result = chain(tryLocalStorageGet('user'), (raw) =>
|
|
209
|
+
* raw === null ? Err({ tag: 'ParseError', raw: '' }) : tryJSONParse<User>(raw))
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
declare function chain<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
|
|
213
|
+
/**
|
|
214
|
+
* A discriminated error shape — every error carries a `tag` so consumers
|
|
215
|
+
* can narrow on it (if/switch, or match for exhaustive handling).
|
|
216
|
+
*
|
|
217
|
+
* @template Tag The string literal discriminant
|
|
218
|
+
* @template Extra Additional fields specific to this error
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* type ParseError = TaggedError<'ParseError', { raw: string }>
|
|
223
|
+
* type StorageError = TaggedError<'StorageError', { key: string }>
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
type TaggedError<Tag extends string, Extra extends object = object> = {
|
|
227
|
+
tag: Tag;
|
|
228
|
+
} & Extra;
|
|
229
|
+
/**
|
|
230
|
+
* Exhaustively matches a Result — requires both an ok and an err handler.
|
|
231
|
+
*
|
|
232
|
+
* @template T The success type
|
|
233
|
+
* @template E The error type
|
|
234
|
+
* @template R The return type of both handlers
|
|
235
|
+
* @param result The Result to match on
|
|
236
|
+
* @param handlers Handlers for the ok and err cases
|
|
237
|
+
* @param handlers.ok Called with the unwrapped value when result is Ok
|
|
238
|
+
* @param handlers.err Called with the unwrapped error when result is Err
|
|
239
|
+
* @returns Whichever handler's return value applies
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* const message = matchResult(result, {
|
|
244
|
+
* ok: (value) => `Got ${value}`,
|
|
245
|
+
* err: (error) => `Failed: ${error.message}`,
|
|
246
|
+
* })
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
declare function matchResult<T, E, R>(result: Result<T, E>, handlers: {
|
|
250
|
+
ok: (value: T) => R;
|
|
251
|
+
err: (error: E) => R;
|
|
252
|
+
}): R;
|
|
253
|
+
/**
|
|
254
|
+
* Exhaustively matches a tagged error union. TypeScript requires a handler
|
|
255
|
+
* for every tag in the union — omitting one is a compile error, and adding
|
|
256
|
+
* a new tag later forces every call site to be updated.
|
|
257
|
+
*
|
|
258
|
+
* @template E The tagged error union type
|
|
259
|
+
* @template Handlers An object with one handler per tag in E
|
|
260
|
+
* @param error The tagged error to match on
|
|
261
|
+
* @param handlers An object mapping each tag to a handler function
|
|
262
|
+
* @returns Whichever handler's return value applies
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* match(result.error, {
|
|
267
|
+
* ParseError: (e) => console.error('bad json:', e.raw),
|
|
268
|
+
* StorageError: (e) => console.error('storage failed:', e.key),
|
|
269
|
+
* // omitting a tag here is a compile error
|
|
270
|
+
* })
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
declare function match<E extends {
|
|
274
|
+
tag: string;
|
|
275
|
+
}, Handlers extends {
|
|
276
|
+
[K in E["tag"]]: (error: Extract<E, {
|
|
277
|
+
tag: K;
|
|
278
|
+
}>) => unknown;
|
|
279
|
+
}>(error: E, handlers: Handlers): ReturnType<Handlers[E["tag"]]>;
|
|
280
|
+
|
|
281
|
+
export { Err, Ok, chain, err, isErr, isOk, match, matchResult, ok, safe };
|
|
282
|
+
export type { Result, TaggedError };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const Ok = (value) => ({ ok: true, value });
|
|
2
|
+
const Err = (error) => ({ ok: false, error });
|
|
3
|
+
function isOk(result) {
|
|
4
|
+
return result.ok === true;
|
|
5
|
+
}
|
|
6
|
+
function isErr(result) {
|
|
7
|
+
return result.ok === false;
|
|
8
|
+
}
|
|
9
|
+
function ok(result) {
|
|
10
|
+
return result.ok === true ? result.value : null;
|
|
11
|
+
}
|
|
12
|
+
function err(result) {
|
|
13
|
+
return result.ok === true ? null : result.error;
|
|
14
|
+
}
|
|
15
|
+
function safe(input, mapError) {
|
|
16
|
+
if (input instanceof Promise) {
|
|
17
|
+
return input.then((data) => Ok(data)).catch((error) => Err(mapError ? mapError(error) : error));
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return Ok(input());
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return Err(mapError ? mapError(error) : error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function chain(result, fn) {
|
|
26
|
+
return isOk(result) ? fn(result.value) : result;
|
|
27
|
+
}
|
|
28
|
+
function matchResult(result, handlers) {
|
|
29
|
+
return result.ok ? handlers.ok(result.value) : handlers.err(result.error);
|
|
30
|
+
}
|
|
31
|
+
function match(error, handlers) {
|
|
32
|
+
return handlers[error.tag](error);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { Err, Ok, chain, err, isErr, isOk, match, matchResult, ok, safe };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { TaggedError, Result } from '../safe/index.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error returned when JSON.parse fails on malformed input.
|
|
5
|
+
*/
|
|
6
|
+
type JSONParseError = TaggedError<"JSONParseError", {
|
|
7
|
+
raw: string;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Error returned when JSON.stringify fails (circular references, BigInt, etc).
|
|
11
|
+
*/
|
|
12
|
+
type JSONStringifyError = TaggedError<"JSONStringifyError">;
|
|
13
|
+
/**
|
|
14
|
+
* Error returned when encodeURIComponent/decodeURIComponent fail on a
|
|
15
|
+
* malformed sequence.
|
|
16
|
+
*/
|
|
17
|
+
type URIError_ = TaggedError<"URIError", {
|
|
18
|
+
input: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Error returned when atob/btoa fail on malformed or non-Latin1 input.
|
|
22
|
+
*/
|
|
23
|
+
type Base64Error = TaggedError<"Base64Error", {
|
|
24
|
+
input: string;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Error returned when a localStorage read/write fails — quota exceeded,
|
|
28
|
+
* storage disabled (e.g. Safari private mode), or serialization failure.
|
|
29
|
+
*/
|
|
30
|
+
type StorageError = TaggedError<"StorageError", {
|
|
31
|
+
key: string;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Error returned when `new URL(...)` fails on an invalid URL string.
|
|
35
|
+
*/
|
|
36
|
+
type URLParseError = TaggedError<"URLParseError", {
|
|
37
|
+
input: string;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Error returned when structuredClone fails on a non-cloneable value
|
|
41
|
+
* (functions, DOM nodes, etc).
|
|
42
|
+
*/
|
|
43
|
+
type StructuredCloneError = TaggedError<"StructuredCloneError">;
|
|
44
|
+
/**
|
|
45
|
+
* Safely parses a JSON string. Wraps JSON.parse, which throws a
|
|
46
|
+
* SyntaxError on invalid JSON.
|
|
47
|
+
*
|
|
48
|
+
* @template T The expected shape of the parsed value
|
|
49
|
+
* @param raw The JSON string to parse
|
|
50
|
+
* @param reviver Optional JSON.parse reviver, called for each member of the object
|
|
51
|
+
* @returns Ok(parsed value) or Err(JSONParseError)
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const result = tryJSONParse<User>(raw)
|
|
56
|
+
* if (isOk(result)) {
|
|
57
|
+
* console.log(result.value.name)
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function tryJSONParse<T = unknown>(raw: string, reviver?: (this: unknown, key: string, value: unknown) => unknown): Result<T, JSONParseError>;
|
|
62
|
+
/**
|
|
63
|
+
* Safely stringifies a value to JSON. Wraps JSON.stringify, which throws
|
|
64
|
+
* a TypeError on circular references or BigInt values.
|
|
65
|
+
*
|
|
66
|
+
* @param value The value to stringify
|
|
67
|
+
* @param replacer Optional JSON.stringify replacer — a transform function, or an allowlist of keys to include
|
|
68
|
+
* @param space Optional JSON.stringify indentation
|
|
69
|
+
* @returns Ok(json string) or Err(JSONStringifyError)
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const result = tryJSONStringify(data)
|
|
74
|
+
* if (isErr(result)) {
|
|
75
|
+
* console.error('could not serialize:', result.error)
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
declare function tryJSONStringify(value: unknown, replacer?: ((this: unknown, key: string, value: unknown) => unknown) | (string | number)[] | null, space?: string | number): Result<string, JSONStringifyError>;
|
|
80
|
+
/**
|
|
81
|
+
* Safely decodes a URI component. Wraps decodeURIComponent, which throws
|
|
82
|
+
* a URIError on malformed percent-encoded sequences.
|
|
83
|
+
*
|
|
84
|
+
* @param input The string to decode
|
|
85
|
+
* @returns Ok(decoded string) or Err(URIError_)
|
|
86
|
+
*/
|
|
87
|
+
declare function tryURIDecode(input: string): Result<string, URIError_>;
|
|
88
|
+
/**
|
|
89
|
+
* Safely encodes a URI component. Wraps encodeURIComponent, which throws
|
|
90
|
+
* a URIError on lone surrogate characters.
|
|
91
|
+
*
|
|
92
|
+
* @param input The string, number, or boolean to encode
|
|
93
|
+
* @returns Ok(encoded string) or Err(URIError_)
|
|
94
|
+
*/
|
|
95
|
+
declare function tryURIEncode(input: string | number | boolean): Result<string, URIError_>;
|
|
96
|
+
/**
|
|
97
|
+
* Safely decodes a base64 string. Wraps atob, which throws a DOMException
|
|
98
|
+
* on malformed base64 or non-Latin1 input.
|
|
99
|
+
*
|
|
100
|
+
* @param input The base64 string to decode
|
|
101
|
+
* @returns Ok(decoded string) or Err(Base64Error)
|
|
102
|
+
*/
|
|
103
|
+
declare function tryBase64Decode(input: string): Result<string, Base64Error>;
|
|
104
|
+
/**
|
|
105
|
+
* Safely encodes a string to base64. Wraps btoa, which throws a
|
|
106
|
+
* DOMException on characters outside the Latin1 range.
|
|
107
|
+
*
|
|
108
|
+
* @param input The string to encode
|
|
109
|
+
* @returns Ok(base64 string) or Err(Base64Error)
|
|
110
|
+
*/
|
|
111
|
+
declare function tryBase64Encode(input: string): Result<string, Base64Error>;
|
|
112
|
+
/**
|
|
113
|
+
* Safely reads a value from localStorage. localStorage.getItem doesn't
|
|
114
|
+
* throw on its own, but this exists so a get→parse pipeline stays
|
|
115
|
+
* consistently Result-shaped throughout (pair with tryJSONParse via chain).
|
|
116
|
+
*
|
|
117
|
+
* @param key The storage key to read
|
|
118
|
+
* @returns Ok(value or null) or Err(StorageError)
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* const result = chain(tryLocalStorageGet('user'), (raw) =>
|
|
123
|
+
* raw === null ? Err({ tag: 'JSONParseError', raw: '' }) : tryJSONParse<User>(raw))
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
declare function tryLocalStorageGet(key: string): Result<string | null, StorageError>;
|
|
127
|
+
/**
|
|
128
|
+
* Safely writes a value to localStorage. Wraps localStorage.setItem, which
|
|
129
|
+
* throws on quota exceeded or when storage is disabled (e.g. Safari
|
|
130
|
+
* private mode).
|
|
131
|
+
*
|
|
132
|
+
* @param key The storage key to write
|
|
133
|
+
* @param value The string value to store
|
|
134
|
+
* @returns Ok(undefined) or Err(StorageError)
|
|
135
|
+
*/
|
|
136
|
+
declare function tryLocalStorageSet(key: string, value: string): Result<void, StorageError>;
|
|
137
|
+
/**
|
|
138
|
+
* Safely constructs a URL. Wraps `new URL(...)`, which throws a TypeError
|
|
139
|
+
* on an invalid URL string.
|
|
140
|
+
*
|
|
141
|
+
* @param input The URL (string or URL) to parse
|
|
142
|
+
* @param base Optional base URL (string or URL) to resolve against
|
|
143
|
+
* @returns Ok(URL) or Err(URLParseError)
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* const result = tryURL(userSuppliedString)
|
|
148
|
+
* if (isOk(result)) {
|
|
149
|
+
* console.log(result.value.hostname)
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
declare function tryURL(input: string | URL, base?: string | URL): Result<URL, URLParseError>;
|
|
154
|
+
/**
|
|
155
|
+
* Safely deep-clones a value. Wraps structuredClone, which throws a
|
|
156
|
+
* DataCloneError on non-cloneable values (functions, DOM nodes, etc).
|
|
157
|
+
*
|
|
158
|
+
* @template T The type of the value to clone
|
|
159
|
+
* @param value The value to clone
|
|
160
|
+
* @param options Optional structuredClone options (e.g. a transfer list)
|
|
161
|
+
* @returns Ok(cloned value) or Err(StructuredCloneError)
|
|
162
|
+
*/
|
|
163
|
+
declare function tryStructuredClone<T>(value: T, options?: StructuredSerializeOptions): Result<T, StructuredCloneError>;
|
|
164
|
+
|
|
165
|
+
export { tryBase64Decode, tryBase64Encode, tryJSONParse, tryJSONStringify, tryLocalStorageGet, tryLocalStorageSet, tryStructuredClone, tryURIDecode, tryURIEncode, tryURL };
|
|
166
|
+
export type { Base64Error, JSONParseError, JSONStringifyError, StorageError, StructuredCloneError, URIError_, URLParseError };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { TaggedError, Result } from '../safe/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error returned when JSON.parse fails on malformed input.
|
|
5
|
+
*/
|
|
6
|
+
type JSONParseError = TaggedError<"JSONParseError", {
|
|
7
|
+
raw: string;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Error returned when JSON.stringify fails (circular references, BigInt, etc).
|
|
11
|
+
*/
|
|
12
|
+
type JSONStringifyError = TaggedError<"JSONStringifyError">;
|
|
13
|
+
/**
|
|
14
|
+
* Error returned when encodeURIComponent/decodeURIComponent fail on a
|
|
15
|
+
* malformed sequence.
|
|
16
|
+
*/
|
|
17
|
+
type URIError_ = TaggedError<"URIError", {
|
|
18
|
+
input: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Error returned when atob/btoa fail on malformed or non-Latin1 input.
|
|
22
|
+
*/
|
|
23
|
+
type Base64Error = TaggedError<"Base64Error", {
|
|
24
|
+
input: string;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Error returned when a localStorage read/write fails — quota exceeded,
|
|
28
|
+
* storage disabled (e.g. Safari private mode), or serialization failure.
|
|
29
|
+
*/
|
|
30
|
+
type StorageError = TaggedError<"StorageError", {
|
|
31
|
+
key: string;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Error returned when `new URL(...)` fails on an invalid URL string.
|
|
35
|
+
*/
|
|
36
|
+
type URLParseError = TaggedError<"URLParseError", {
|
|
37
|
+
input: string;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Error returned when structuredClone fails on a non-cloneable value
|
|
41
|
+
* (functions, DOM nodes, etc).
|
|
42
|
+
*/
|
|
43
|
+
type StructuredCloneError = TaggedError<"StructuredCloneError">;
|
|
44
|
+
/**
|
|
45
|
+
* Safely parses a JSON string. Wraps JSON.parse, which throws a
|
|
46
|
+
* SyntaxError on invalid JSON.
|
|
47
|
+
*
|
|
48
|
+
* @template T The expected shape of the parsed value
|
|
49
|
+
* @param raw The JSON string to parse
|
|
50
|
+
* @param reviver Optional JSON.parse reviver, called for each member of the object
|
|
51
|
+
* @returns Ok(parsed value) or Err(JSONParseError)
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const result = tryJSONParse<User>(raw)
|
|
56
|
+
* if (isOk(result)) {
|
|
57
|
+
* console.log(result.value.name)
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function tryJSONParse<T = unknown>(raw: string, reviver?: (this: unknown, key: string, value: unknown) => unknown): Result<T, JSONParseError>;
|
|
62
|
+
/**
|
|
63
|
+
* Safely stringifies a value to JSON. Wraps JSON.stringify, which throws
|
|
64
|
+
* a TypeError on circular references or BigInt values.
|
|
65
|
+
*
|
|
66
|
+
* @param value The value to stringify
|
|
67
|
+
* @param replacer Optional JSON.stringify replacer — a transform function, or an allowlist of keys to include
|
|
68
|
+
* @param space Optional JSON.stringify indentation
|
|
69
|
+
* @returns Ok(json string) or Err(JSONStringifyError)
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const result = tryJSONStringify(data)
|
|
74
|
+
* if (isErr(result)) {
|
|
75
|
+
* console.error('could not serialize:', result.error)
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
declare function tryJSONStringify(value: unknown, replacer?: ((this: unknown, key: string, value: unknown) => unknown) | (string | number)[] | null, space?: string | number): Result<string, JSONStringifyError>;
|
|
80
|
+
/**
|
|
81
|
+
* Safely decodes a URI component. Wraps decodeURIComponent, which throws
|
|
82
|
+
* a URIError on malformed percent-encoded sequences.
|
|
83
|
+
*
|
|
84
|
+
* @param input The string to decode
|
|
85
|
+
* @returns Ok(decoded string) or Err(URIError_)
|
|
86
|
+
*/
|
|
87
|
+
declare function tryURIDecode(input: string): Result<string, URIError_>;
|
|
88
|
+
/**
|
|
89
|
+
* Safely encodes a URI component. Wraps encodeURIComponent, which throws
|
|
90
|
+
* a URIError on lone surrogate characters.
|
|
91
|
+
*
|
|
92
|
+
* @param input The string, number, or boolean to encode
|
|
93
|
+
* @returns Ok(encoded string) or Err(URIError_)
|
|
94
|
+
*/
|
|
95
|
+
declare function tryURIEncode(input: string | number | boolean): Result<string, URIError_>;
|
|
96
|
+
/**
|
|
97
|
+
* Safely decodes a base64 string. Wraps atob, which throws a DOMException
|
|
98
|
+
* on malformed base64 or non-Latin1 input.
|
|
99
|
+
*
|
|
100
|
+
* @param input The base64 string to decode
|
|
101
|
+
* @returns Ok(decoded string) or Err(Base64Error)
|
|
102
|
+
*/
|
|
103
|
+
declare function tryBase64Decode(input: string): Result<string, Base64Error>;
|
|
104
|
+
/**
|
|
105
|
+
* Safely encodes a string to base64. Wraps btoa, which throws a
|
|
106
|
+
* DOMException on characters outside the Latin1 range.
|
|
107
|
+
*
|
|
108
|
+
* @param input The string to encode
|
|
109
|
+
* @returns Ok(base64 string) or Err(Base64Error)
|
|
110
|
+
*/
|
|
111
|
+
declare function tryBase64Encode(input: string): Result<string, Base64Error>;
|
|
112
|
+
/**
|
|
113
|
+
* Safely reads a value from localStorage. localStorage.getItem doesn't
|
|
114
|
+
* throw on its own, but this exists so a get→parse pipeline stays
|
|
115
|
+
* consistently Result-shaped throughout (pair with tryJSONParse via chain).
|
|
116
|
+
*
|
|
117
|
+
* @param key The storage key to read
|
|
118
|
+
* @returns Ok(value or null) or Err(StorageError)
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* const result = chain(tryLocalStorageGet('user'), (raw) =>
|
|
123
|
+
* raw === null ? Err({ tag: 'JSONParseError', raw: '' }) : tryJSONParse<User>(raw))
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
declare function tryLocalStorageGet(key: string): Result<string | null, StorageError>;
|
|
127
|
+
/**
|
|
128
|
+
* Safely writes a value to localStorage. Wraps localStorage.setItem, which
|
|
129
|
+
* throws on quota exceeded or when storage is disabled (e.g. Safari
|
|
130
|
+
* private mode).
|
|
131
|
+
*
|
|
132
|
+
* @param key The storage key to write
|
|
133
|
+
* @param value The string value to store
|
|
134
|
+
* @returns Ok(undefined) or Err(StorageError)
|
|
135
|
+
*/
|
|
136
|
+
declare function tryLocalStorageSet(key: string, value: string): Result<void, StorageError>;
|
|
137
|
+
/**
|
|
138
|
+
* Safely constructs a URL. Wraps `new URL(...)`, which throws a TypeError
|
|
139
|
+
* on an invalid URL string.
|
|
140
|
+
*
|
|
141
|
+
* @param input The URL (string or URL) to parse
|
|
142
|
+
* @param base Optional base URL (string or URL) to resolve against
|
|
143
|
+
* @returns Ok(URL) or Err(URLParseError)
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* const result = tryURL(userSuppliedString)
|
|
148
|
+
* if (isOk(result)) {
|
|
149
|
+
* console.log(result.value.hostname)
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
declare function tryURL(input: string | URL, base?: string | URL): Result<URL, URLParseError>;
|
|
154
|
+
/**
|
|
155
|
+
* Safely deep-clones a value. Wraps structuredClone, which throws a
|
|
156
|
+
* DataCloneError on non-cloneable values (functions, DOM nodes, etc).
|
|
157
|
+
*
|
|
158
|
+
* @template T The type of the value to clone
|
|
159
|
+
* @param value The value to clone
|
|
160
|
+
* @param options Optional structuredClone options (e.g. a transfer list)
|
|
161
|
+
* @returns Ok(cloned value) or Err(StructuredCloneError)
|
|
162
|
+
*/
|
|
163
|
+
declare function tryStructuredClone<T>(value: T, options?: StructuredSerializeOptions): Result<T, StructuredCloneError>;
|
|
164
|
+
|
|
165
|
+
export { tryBase64Decode, tryBase64Encode, tryJSONParse, tryJSONStringify, tryLocalStorageGet, tryLocalStorageSet, tryStructuredClone, tryURIDecode, tryURIEncode, tryURL };
|
|
166
|
+
export type { Base64Error, JSONParseError, JSONStringifyError, StorageError, StructuredCloneError, URIError_, URLParseError };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { safe } from '../safe/index.mjs';
|
|
2
|
+
|
|
3
|
+
function tryJSONParse(raw, reviver) {
|
|
4
|
+
return safe(
|
|
5
|
+
() => JSON.parse(raw, reviver),
|
|
6
|
+
() => ({ tag: "JSONParseError", raw })
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
function tryJSONStringify(value, replacer, space) {
|
|
10
|
+
return safe(
|
|
11
|
+
() => JSON.stringify(value, replacer, space),
|
|
12
|
+
() => ({ tag: "JSONStringifyError" })
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function tryURIDecode(input) {
|
|
16
|
+
return safe(
|
|
17
|
+
() => decodeURIComponent(input),
|
|
18
|
+
() => ({ tag: "URIError", input })
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
function tryURIEncode(input) {
|
|
22
|
+
return safe(
|
|
23
|
+
() => encodeURIComponent(input),
|
|
24
|
+
() => ({ tag: "URIError", input: String(input) })
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function tryBase64Decode(input) {
|
|
28
|
+
return safe(
|
|
29
|
+
() => atob(input),
|
|
30
|
+
() => ({ tag: "Base64Error", input })
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
function tryBase64Encode(input) {
|
|
34
|
+
return safe(
|
|
35
|
+
() => btoa(input),
|
|
36
|
+
() => ({ tag: "Base64Error", input })
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function tryLocalStorageGet(key) {
|
|
40
|
+
return safe(
|
|
41
|
+
() => localStorage.getItem(key),
|
|
42
|
+
() => ({ tag: "StorageError", key })
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function tryLocalStorageSet(key, value) {
|
|
46
|
+
return safe(
|
|
47
|
+
() => localStorage.setItem(key, value),
|
|
48
|
+
() => ({ tag: "StorageError", key })
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function tryURL(input, base) {
|
|
52
|
+
return safe(
|
|
53
|
+
() => new URL(input, base),
|
|
54
|
+
() => ({ tag: "URLParseError", input: String(input) })
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function tryStructuredClone(value, options) {
|
|
58
|
+
return safe(
|
|
59
|
+
() => structuredClone(value, options),
|
|
60
|
+
() => ({ tag: "StructuredCloneError" })
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { tryBase64Decode, tryBase64Encode, tryJSONParse, tryJSONStringify, tryLocalStorageGet, tryLocalStorageSet, tryStructuredClone, tryURIDecode, tryURIEncode, tryURL };
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crbroughton/failsafe",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"packageManager": "pnpm@9.15.0",
|
|
6
|
+
"description": "Type-safe error handling utilities for TypeScript",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/safe/index.d.mts",
|
|
12
|
+
"import": "./dist/safe/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./try": {
|
|
15
|
+
"types": "./dist/try/index.d.mts",
|
|
16
|
+
"import": "./dist/try/index.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./pipe": {
|
|
19
|
+
"types": "./dist/pipe/index.d.mts",
|
|
20
|
+
"import": "./dist/pipe/index.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/safe/index.mjs",
|
|
24
|
+
"types": "./dist/safe/index.d.mts",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=24"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "unbuild",
|
|
33
|
+
"test": "vitest run --coverage",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"lint": "eslint .",
|
|
36
|
+
"lint:fix": "eslint . --fix",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"changeset": "changeset",
|
|
39
|
+
"version": "changeset version",
|
|
40
|
+
"release": "pnpm build && changeset publish"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@antfu/eslint-config": "^3.9.2",
|
|
44
|
+
"@changesets/cli": "^3.0.3",
|
|
45
|
+
"@vitest/coverage-v8": "^2.1.8",
|
|
46
|
+
"eslint": "^9.17.0",
|
|
47
|
+
"happy-dom": "^15.11.7",
|
|
48
|
+
"nx": "^20.3.0",
|
|
49
|
+
"typescript": "^5.7.2",
|
|
50
|
+
"unbuild": "^3.0.1",
|
|
51
|
+
"vitest": "^2.1.8"
|
|
52
|
+
}
|
|
53
|
+
}
|