Skip to content

Commit ead5ed4

Browse files
authored
Merge pull request #2 from maxmorozoff/dev
feat: enhance error handling and improve TypeScript support
2 parents 29d097c + 23d5176 commit ead5ed4

File tree

7 files changed

+329
-79
lines changed

7 files changed

+329
-79
lines changed

.changeset/warm-eggs-show.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@maxmorozoff/try-catch-tuple": minor
3+
---
4+
5+
#### Added
6+
7+
- Introduced `.errors<E>()` method to allow specifying expected error types.
8+
- Ensured non-Error thrown values are wrapped properly in an `Error` instance with `cause`.
9+
- Added more tests to cover edge cases and ensure robustness.
10+
11+
#### Improved
12+
13+
- Refactored `tryCatch`, `tryCatch.sync`, and `tryCatch.async` to support generic error types.
14+
- Introduced `TryCatchFunc` and `TryCatchResult` for improved type safety.
15+
- Updated `typescript` version range in `peerDependencies` to `^5.0.0`, supporting all 5.x versions.
16+
- Added `peerDependenciesMeta` to mark `typescript` as an optional dependency. This allows consumers of the package to choose not to install `typescript` while still ensuring compatibility with TypeScript 5.x.
17+
18+
#### Fixed
19+
20+
- `handleError` now correctly preserves the original thrown value under `cause`.

README.md

Lines changed: 142 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ See [Theo's gist for more details](https://gist.github.com/t3dotgg/a486c4ae66d32
1010
- Returns a structured tuple `[data, error]`.
1111
- Provides named operations for better debugging.
1212
- Includes `tryCatch.sync` and `tryCatch.async` for explicit handling.
13+
- Allows custom error types via `.errors<E>()`
14+
- Ensures all thrown values become `Error` instances
1315

1416
## Installation
1517

@@ -24,60 +26,150 @@ npm install @maxmorozoff/try-catch-tuple
2426
```ts
2527
import { tryCatch } from "@maxmorozoff/try-catch-tuple";
2628

27-
const [result, error] = tryCatch.sync(() => JSON.parse("73"));
28-
console.log(result); // 73
29-
console.log(error); // null
29+
function main() {
30+
const [result, error] = tryCatch(() => JSON.parse("73") as number);
31+
// ^? const result: number | null
32+
33+
if (!error) return result; // ✅ result: number
34+
console.log(error); // ❌ Error
35+
// ^? const error: Error
36+
}
3037
```
3138

32-
### Handling Errors
39+
### Named Operations for Debugging
3340

3441
```ts
3542
const [result, error] = tryCatch((): void => {
36-
throw new Error("Something went wrong");
37-
});
43+
throw new Error("Failed to fetch data");
44+
}, "Fetch Data");
45+
46+
console.log(error?.message); // "Operation \"Fetch Data\" failed: Failed to fetch data"
47+
```
48+
49+
### Using `tryCatch.sync`
3850

39-
console.log(result); // null
40-
console.log(error?.message); // "Something went wrong"
51+
```ts
52+
function main() {
53+
const [result, error] = tryCatch.sync(() => JSON.parse("73") as number);
54+
// ^? const result: number | null
55+
if (!error) return result;
56+
// ^? const result: number
57+
error;
58+
// ^? const error: Error
59+
result;
60+
// ^? const result: null
61+
}
4162
```
4263

43-
### Asynchronous Usage with Errors
64+
### Using `tryCatch.async`
4465

4566
```ts
46-
const [result, error] = await tryCatch(async () => {
47-
throw new Error("Network request failed");
67+
async function main() {
68+
const [result, error] = await tryCatch.async(
69+
// ^? const result: number | null
70+
async () => JSON.parse("73") as number
71+
);
72+
if (!error) return result;
73+
// ^? const result: number
74+
error;
75+
// ^? const error: Error
76+
result;
77+
// ^? const result: null
78+
}
79+
```
80+
81+
### Handling Errors
82+
83+
#### Ensuring All Thrown Values Are Error Instances
84+
85+
If a thrown value is **not an instance of** `Error`, it gets wrapped:
86+
87+
```ts
88+
const [result, error] = tryCatch((): void => {
89+
throw "Something went wrong";
4890
});
4991

50-
console.log(result); // null
51-
console.log(error?.message); // "Network request failed"
92+
console.log(error.message); // "Something went wrong"
93+
// ^? const error: Error
94+
95+
const [data, nullError] = tryCatch((): void => {
96+
throw null;
97+
});
98+
99+
console.log(nullError.message); // "null"
100+
// ^? const error: Error
101+
console.log(nullError.cause); // null
52102
```
53103

54-
### Named Operations for Debugging
104+
This ensures tryCatch always provides a proper error object.
105+
106+
#### Extending Error types
107+
108+
##### Option 1: Manually Set Result and Error Type
55109

56110
```ts
57-
const [result, error] = tryCatch(() => {
58-
throw new Error("Failed to fetch data");
59-
}, "Fetch Data");
111+
type User = { id: number; name: string };
60112

61-
console.log(error?.message); // "Operation \"Fetch Data\" failed: Failed to fetch data"
113+
async function main() {
114+
const [user, error] = await tryCatch<Promise<User>, SyntaxError>(
115+
fetchUser(1)
116+
);
117+
118+
if (!error) return user; // ✅ user: User
119+
console.error(error); // ❌ SyntaxError | Error
120+
}
62121
```
63122

64-
### Using `tryCatch.sync`
123+
##### Option 2: Using `tryCatch.errors<E>()` Helper
65124

66125
```ts
67-
const [result, error] = tryCatch.sync(() => JSON.parse("INVALID_JSON"));
68-
console.log(result); // null
69-
console.log(error?.message); // "Unexpected token I in JSON"
126+
async function main() {
127+
const [user, error] = await tryCatch.errors<SyntaxError>()(fetchUser(1));
128+
129+
if (!error) return user; // ✅ user: User
130+
console.error(error); // ❌ Error | SyntaxError
131+
}
70132
```
71133

72-
### Using `tryCatch.async`
134+
### Wrapping Functions for Reuse
135+
136+
To avoid repetitive tryCatch calls, you can wrap functions:
73137

74138
```ts
75-
const [result, error] = await tryCatch.async(async () => {
76-
throw new Error("Async operation failed");
77-
});
139+
const getUser = (id: number) =>
140+
tryCatch
141+
.errors<RangeError>()
142+
.errors<SyntaxError | TypeError | DOMException>()
143+
.async(fetchUser(id));
144+
145+
// Or simply:
146+
// const getUser = (id: number) => tryCatch(fetchUser(id));
147+
148+
async function main() {
149+
const [user, error] = await getUser(1);
150+
151+
if (!error) return user; // ✅ user: User
152+
console.error(error); // ❌ Error | RangeError | SyntaxError | TypeError | DOMException
153+
}
154+
```
78155

79-
console.log(result); // null
80-
console.log(error?.message); // "Async operation failed"
156+
### Using in React Server Components (RSC)
157+
158+
```tsx
159+
const getUser = (id: number) =>
160+
tryCatch.errors<SyntaxError | TypeError | DOMException>()(fetchUser(id));
161+
162+
async function UserPage({ id }: { id: number }) {
163+
const [user, error] = await getUser(id);
164+
165+
if (!error) return <div>Hello {user.name}!</div>;
166+
167+
if (error instanceof SyntaxError) {
168+
return <div>Error: {error.message}</div>;
169+
}
170+
171+
return <div>Not found</div>;
172+
}
81173
```
82174

83175
### Comparing `tryCatch` with `try...catch`
@@ -89,11 +181,11 @@ async function goodFunc() {
89181
}
90182

91183
async function badFunc() {
92-
throw "no data";
93-
return "";
184+
if (true) throw "no data";
185+
return "some data";
94186
}
95187

96-
// Using tryCatch
188+
// Using tryCatch
97189
const getData = async () => {
98190
let [data, err] = await tryCatch(badFunc);
99191
if (!err) return Response.json({ data });
@@ -107,7 +199,7 @@ const getData = async () => {
107199
return Response.error();
108200
};
109201

110-
// Using tryCatch with constants
202+
// Using tryCatch with constants
111203
const getDataConst = async () => {
112204
const [data1, err1] = await tryCatch(badFunc);
113205
if (!err1) return Response.json({ data: data1 });
@@ -121,7 +213,7 @@ const getDataConst = async () => {
121213
return Response.error();
122214
};
123215

124-
// Using try...catch
216+
// Using traditional try...catch (deep nesting)
125217
const getDataStandard = async () => {
126218
try {
127219
const data = await badFunc();
@@ -144,22 +236,31 @@ const getDataStandard = async () => {
144236

145237
## API Reference
146238

147-
### `tryCatch<T>(fn: (() => T) | T, operationName?: string): Result<T>`
239+
### Main Function
148240

149-
Handles both values and functions that may throw errors.
241+
```ts
242+
tryCatch<T, E extends Error = never>(fn?: (() => T) | T | Promise<T> | (() => Promise<T>), operationName?: string): Result<T, E>
243+
```
150244
151-
### `tryCatch.sync<T>(fn: () => T, operationName?: string): Result<T>`
245+
- Handles values, sync/async functions
246+
- Automatically detects Promises
152247
153-
Explicitly handles synchronous operations.
248+
### Explicit Synchronous Handling
154249
155-
### `tryCatch.async<T>(fn: Promise<T> | (() => Promise<T>), operationName?: string): Promise<Result<T>>`
250+
```ts
251+
tryCatch.sync<T, E extends Error = never>(fn: () => T, operationName?: string): Result<T, E>
252+
```
156253
157-
Explicitly handles asynchronous operations.
254+
### Explicit Asynchronous Handling
255+
256+
```ts
257+
tryCatch.async<T, E extends Error = never>(fn: Promise<T> | (() => Promise<T>), operationName?: string): Promise<Result<T, E>>
258+
```
158259
159260
## Result Type
160261
161262
```ts
162-
type Result<T, E = Error> = [data: T | null, error: E | null];
263+
type Result<T, E = Error> = [data: T, error: null] | [data: null, error: E];
163264
```
164265
165266
## Edge Cases
@@ -169,7 +270,7 @@ tryCatch(); // Returns [undefined, null]
169270
tryCatch(null); // Returns [null, null]
170271
tryCatch(() => {
171272
throw new Error("Unexpected Error");
172-
}); // Handles thrown errors
273+
}); // Returns [null, Error]
173274
tryCatch(Promise.reject(new Error("Promise rejected"))); // Handles rejected promises
174275
```
175276

bun.lock

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
"tsup": "^8.4.0",
1111
},
1212
"peerDependencies": {
13-
"typescript": "^5.8.2",
13+
"typescript": "^5.0.0",
1414
},
15+
"optionalPeers": [
16+
"typescript",
17+
],
1518
},
1619
},
1720
"packages": {

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@
5050
"tsup": "^8.4.0"
5151
},
5252
"peerDependencies": {
53-
"typescript": "^5.8.2"
53+
"typescript": "^5.0.0"
54+
},
55+
"peerDependenciesMeta": {
56+
"typescript": {
57+
"optional": true
58+
}
5459
}
5560
}

0 commit comments

Comments
 (0)