go-go-try 4.1.2 → 6.0.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 +2 -1
- package/README.md +15 -15
- package/dist/index.cjs +34 -44
- package/dist/index.d.cts +53 -8
- package/dist/index.d.mts +53 -8
- package/dist/index.mjs +31 -44
- package/package.json +49 -58
- package/src/index.ts +73 -85
- package/test.ts +118 -86
- package/tsconfig.json +1 -17
- package/jest.config.ts +0 -36
package/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 Alisson Cavalcante Agiani thelinuxlich@gmail.com
|
|
4
|
+
Copyright (c) 2022 Antonio Stoilkov hello@astoilkov.com https://astoilkov.com
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## Why
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- `go-go-try` is inspired by Golang error catching.
|
|
7
|
+
- Supports sync/async functions.
|
|
8
|
+
- Allows you to capture the thrown error.
|
|
9
|
+
- Written in TypeScript. The types are written in a way that reduce developer errors.
|
|
10
|
+
- Inspired by Golang error catching.
|
|
11
|
+
- Zero dependencies.
|
|
13
12
|
|
|
14
13
|
Why not just `try`/`catch`?
|
|
15
14
|
|
|
@@ -34,7 +33,7 @@ npm install go-go-try
|
|
|
34
33
|
## Usage
|
|
35
34
|
|
|
36
35
|
```ts
|
|
37
|
-
import { goTry,
|
|
36
|
+
import { goTry, goTryRaw } from 'go-go-try'
|
|
38
37
|
|
|
39
38
|
// tries to parse todos, returns empty array if it fails
|
|
40
39
|
const [_, value = []] = goTry(() => JSON.parse(todos))
|
|
@@ -43,25 +42,26 @@ const [_, value = []] = goTry(() => JSON.parse(todos))
|
|
|
43
42
|
const [_, todos = []] = await goTry(fetchTodos())
|
|
44
43
|
|
|
45
44
|
// fetch todos, fallback to empty array, send error to your error tracking service
|
|
46
|
-
const [err, todos = []] = await goTry(fetchTodos())
|
|
47
|
-
|
|
45
|
+
const [err, todos = []] = await goTry(fetchTodos()) // err is string | undefined
|
|
46
|
+
if (err) sendToErrorTrackingService(err)
|
|
48
47
|
|
|
49
|
-
//
|
|
50
|
-
const value =
|
|
48
|
+
// goTry extracts the error message from the error object, if you want the raw error object, use goTryRaw
|
|
49
|
+
const [err, value] = goTryRaw<Error>(() => JSON.parse('{/}')) // err will be unknown, value will be always T and you can add a Error type as the first generic argument to avoid checking `instanceof Error`
|
|
51
50
|
|
|
52
|
-
//
|
|
53
|
-
const [err,
|
|
51
|
+
// fetch todos, fallback to empty array, send error to your error tracking service
|
|
52
|
+
const [err, todos = []] = await goTryRaw<SomeErrorType>(fetchTodos()) // err is SomeErrorType | undefined
|
|
53
|
+
if (err) sendToErrorTrackingService(err)
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
## API
|
|
57
57
|
|
|
58
58
|
**First parameter** accepts:
|
|
59
59
|
|
|
60
|
-
- synchronous function
|
|
61
|
-
- asynchronous function / Promise
|
|
60
|
+
- synchronous/asynchronous function / Promise
|
|
62
61
|
|
|
63
62
|
**Returns** a tuple with the possible error and result (Golang style)
|
|
64
63
|
|
|
64
|
+
|
|
65
65
|
If you use TypeScript, the types are well defined and won't let you make a mistake.
|
|
66
66
|
|
|
67
67
|
## Inspiration
|
package/dist/index.cjs
CHANGED
|
@@ -1,67 +1,57 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
function isSuccess(result) {
|
|
4
|
+
return result[0] === void 0;
|
|
5
|
+
}
|
|
6
|
+
function isFailure(result) {
|
|
7
|
+
return result[0] !== void 0;
|
|
8
|
+
}
|
|
9
|
+
function success(value) {
|
|
10
|
+
return [void 0, value];
|
|
7
11
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
function failure(error) {
|
|
13
|
+
return [error, void 0];
|
|
14
|
+
}
|
|
15
|
+
function getErrorMessage(error) {
|
|
16
|
+
if (typeof error === "string") return error;
|
|
17
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
18
|
+
return error.message;
|
|
11
19
|
}
|
|
12
20
|
try {
|
|
13
|
-
return
|
|
21
|
+
return JSON.stringify(error);
|
|
14
22
|
} catch {
|
|
15
|
-
return
|
|
23
|
+
return String(error);
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
|
-
function
|
|
19
|
-
return
|
|
20
|
-
}
|
|
21
|
-
function isPromise(p) {
|
|
22
|
-
return pIsPromise(p);
|
|
26
|
+
function isPromiseLike(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
23
28
|
}
|
|
24
29
|
function goTry(value) {
|
|
25
|
-
|
|
30
|
+
if (isPromiseLike(value)) {
|
|
31
|
+
return Promise.resolve(value).then((value2) => success(value2)).catch((err) => failure(getErrorMessage(err)));
|
|
32
|
+
}
|
|
26
33
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return Promise.resolve(unwrappedValue).then((value2) => [void 0, value2]).catch((err) => [getErrorMessage(err), void 0]);
|
|
30
|
-
}
|
|
31
|
-
return [void 0, unwrappedValue];
|
|
34
|
+
const result = typeof value === "function" ? value() : value;
|
|
35
|
+
return success(result);
|
|
32
36
|
} catch (err) {
|
|
33
|
-
return
|
|
37
|
+
return failure(getErrorMessage(err));
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
function goTryRaw(value) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
unwrappedValue = typeof value === "function" ? value() : value;
|
|
40
|
-
if (isPromise(unwrappedValue)) {
|
|
41
|
-
return Promise.resolve(unwrappedValue).then((value2) => [void 0, value2]).catch((err) => [err, void 0]);
|
|
42
|
-
}
|
|
43
|
-
return [void 0, unwrappedValue];
|
|
44
|
-
} catch (err) {
|
|
45
|
-
return [err, void 0];
|
|
41
|
+
if (isPromiseLike(value)) {
|
|
42
|
+
return Promise.resolve(value).then((value2) => success(value2)).catch((err) => failure(err));
|
|
46
43
|
}
|
|
47
|
-
}
|
|
48
|
-
async function goExpect(value, error) {
|
|
49
|
-
const _error = error ?? ((e) => e);
|
|
50
44
|
try {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const [err, res] = await goTry(unwrappedValue);
|
|
54
|
-
if (err !== void 0) {
|
|
55
|
-
throw new Error(err);
|
|
56
|
-
}
|
|
57
|
-
return res;
|
|
58
|
-
}
|
|
59
|
-
return unwrappedValue;
|
|
45
|
+
const result = typeof value === "function" ? value() : value;
|
|
46
|
+
return success(result);
|
|
60
47
|
} catch (err) {
|
|
61
|
-
|
|
48
|
+
return failure(err);
|
|
62
49
|
}
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
exports.
|
|
52
|
+
exports.failure = failure;
|
|
66
53
|
exports.goTry = goTry;
|
|
67
54
|
exports.goTryRaw = goTryRaw;
|
|
55
|
+
exports.isFailure = isFailure;
|
|
56
|
+
exports.isSuccess = isSuccess;
|
|
57
|
+
exports.success = success;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Type for a successful result
|
|
3
|
+
*/
|
|
4
|
+
type Success<T> = readonly [undefined, T];
|
|
5
|
+
/**
|
|
6
|
+
* Type for an error result
|
|
7
|
+
*/
|
|
8
|
+
type Failure<E> = readonly [E, undefined];
|
|
9
|
+
/**
|
|
10
|
+
* Type for a result that can be either success or failure
|
|
11
|
+
*/
|
|
12
|
+
type Result<E, T> = Success<T> | Failure<E>;
|
|
13
|
+
/**
|
|
14
|
+
* Type guard to check if a result is a success
|
|
15
|
+
*/
|
|
16
|
+
declare function isSuccess<E, T>(result: Result<E, T>): result is Success<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Type guard to check if a result is a failure
|
|
19
|
+
*/
|
|
20
|
+
declare function isFailure<E, T>(result: Result<E, T>): result is Failure<E>;
|
|
21
|
+
/**
|
|
22
|
+
* Helper to create a success result
|
|
23
|
+
*/
|
|
24
|
+
declare function success<T>(value: T): Success<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Helper to create a failure result
|
|
27
|
+
*/
|
|
28
|
+
declare function failure<E>(error: E): Failure<E>;
|
|
29
|
+
/**
|
|
30
|
+
* Executes a synchronous function and returns a Result
|
|
31
|
+
*/
|
|
32
|
+
declare function goTry<T>(fn: () => T): Result<string, T>;
|
|
33
|
+
/**
|
|
34
|
+
* Handles a value directly and returns a Result
|
|
35
|
+
*/
|
|
36
|
+
declare function goTry<T>(value: T): Result<string, T>;
|
|
37
|
+
/**
|
|
38
|
+
* Handles a promise and returns a Promise<Result>
|
|
39
|
+
*/
|
|
40
|
+
declare function goTry<T>(promise: PromiseLike<T>): Promise<Result<string, T>>;
|
|
41
|
+
/**
|
|
42
|
+
* Executes a synchronous function and returns a Result with the raw error
|
|
43
|
+
*/
|
|
44
|
+
declare function goTryRaw<E = unknown, T = unknown>(fn: () => T): Result<E, T>;
|
|
45
|
+
/**
|
|
46
|
+
* Handles a value directly and returns a Result
|
|
47
|
+
*/
|
|
48
|
+
declare function goTryRaw<E = unknown, T = unknown>(value: T): Result<E, T>;
|
|
49
|
+
/**
|
|
50
|
+
* Handles a promise and returns a Promise<Result> with the raw error
|
|
51
|
+
*/
|
|
52
|
+
declare function goTryRaw<E = unknown, T = unknown>(promise: PromiseLike<T>): Promise<Result<E, T>>;
|
|
8
53
|
|
|
9
|
-
export {
|
|
54
|
+
export { type Failure, type Result, type Success, failure, goTry, goTryRaw, isFailure, isSuccess, success };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Type for a successful result
|
|
3
|
+
*/
|
|
4
|
+
type Success<T> = readonly [undefined, T];
|
|
5
|
+
/**
|
|
6
|
+
* Type for an error result
|
|
7
|
+
*/
|
|
8
|
+
type Failure<E> = readonly [E, undefined];
|
|
9
|
+
/**
|
|
10
|
+
* Type for a result that can be either success or failure
|
|
11
|
+
*/
|
|
12
|
+
type Result<E, T> = Success<T> | Failure<E>;
|
|
13
|
+
/**
|
|
14
|
+
* Type guard to check if a result is a success
|
|
15
|
+
*/
|
|
16
|
+
declare function isSuccess<E, T>(result: Result<E, T>): result is Success<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Type guard to check if a result is a failure
|
|
19
|
+
*/
|
|
20
|
+
declare function isFailure<E, T>(result: Result<E, T>): result is Failure<E>;
|
|
21
|
+
/**
|
|
22
|
+
* Helper to create a success result
|
|
23
|
+
*/
|
|
24
|
+
declare function success<T>(value: T): Success<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Helper to create a failure result
|
|
27
|
+
*/
|
|
28
|
+
declare function failure<E>(error: E): Failure<E>;
|
|
29
|
+
/**
|
|
30
|
+
* Executes a synchronous function and returns a Result
|
|
31
|
+
*/
|
|
32
|
+
declare function goTry<T>(fn: () => T): Result<string, T>;
|
|
33
|
+
/**
|
|
34
|
+
* Handles a value directly and returns a Result
|
|
35
|
+
*/
|
|
36
|
+
declare function goTry<T>(value: T): Result<string, T>;
|
|
37
|
+
/**
|
|
38
|
+
* Handles a promise and returns a Promise<Result>
|
|
39
|
+
*/
|
|
40
|
+
declare function goTry<T>(promise: PromiseLike<T>): Promise<Result<string, T>>;
|
|
41
|
+
/**
|
|
42
|
+
* Executes a synchronous function and returns a Result with the raw error
|
|
43
|
+
*/
|
|
44
|
+
declare function goTryRaw<E = unknown, T = unknown>(fn: () => T): Result<E, T>;
|
|
45
|
+
/**
|
|
46
|
+
* Handles a value directly and returns a Result
|
|
47
|
+
*/
|
|
48
|
+
declare function goTryRaw<E = unknown, T = unknown>(value: T): Result<E, T>;
|
|
49
|
+
/**
|
|
50
|
+
* Handles a promise and returns a Promise<Result> with the raw error
|
|
51
|
+
*/
|
|
52
|
+
declare function goTryRaw<E = unknown, T = unknown>(promise: PromiseLike<T>): Promise<Result<E, T>>;
|
|
8
53
|
|
|
9
|
-
export {
|
|
54
|
+
export { type Failure, type Result, type Success, failure, goTry, goTryRaw, isFailure, isSuccess, success };
|
package/dist/index.mjs
CHANGED
|
@@ -1,63 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
function isSuccess(result) {
|
|
2
|
+
return result[0] === void 0;
|
|
3
|
+
}
|
|
4
|
+
function isFailure(result) {
|
|
5
|
+
return result[0] !== void 0;
|
|
6
|
+
}
|
|
7
|
+
function success(value) {
|
|
8
|
+
return [void 0, value];
|
|
5
9
|
}
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
function failure(error) {
|
|
11
|
+
return [error, void 0];
|
|
12
|
+
}
|
|
13
|
+
function getErrorMessage(error) {
|
|
14
|
+
if (typeof error === "string") return error;
|
|
15
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
16
|
+
return error.message;
|
|
9
17
|
}
|
|
10
18
|
try {
|
|
11
|
-
return
|
|
19
|
+
return JSON.stringify(error);
|
|
12
20
|
} catch {
|
|
13
|
-
return
|
|
21
|
+
return String(error);
|
|
14
22
|
}
|
|
15
23
|
}
|
|
16
|
-
function
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
function isPromise(p) {
|
|
20
|
-
return pIsPromise(p);
|
|
24
|
+
function isPromiseLike(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
21
26
|
}
|
|
22
27
|
function goTry(value) {
|
|
23
|
-
|
|
28
|
+
if (isPromiseLike(value)) {
|
|
29
|
+
return Promise.resolve(value).then((value2) => success(value2)).catch((err) => failure(getErrorMessage(err)));
|
|
30
|
+
}
|
|
24
31
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return Promise.resolve(unwrappedValue).then((value2) => [void 0, value2]).catch((err) => [getErrorMessage(err), void 0]);
|
|
28
|
-
}
|
|
29
|
-
return [void 0, unwrappedValue];
|
|
32
|
+
const result = typeof value === "function" ? value() : value;
|
|
33
|
+
return success(result);
|
|
30
34
|
} catch (err) {
|
|
31
|
-
return
|
|
35
|
+
return failure(getErrorMessage(err));
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
function goTryRaw(value) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
unwrappedValue = typeof value === "function" ? value() : value;
|
|
38
|
-
if (isPromise(unwrappedValue)) {
|
|
39
|
-
return Promise.resolve(unwrappedValue).then((value2) => [void 0, value2]).catch((err) => [err, void 0]);
|
|
40
|
-
}
|
|
41
|
-
return [void 0, unwrappedValue];
|
|
42
|
-
} catch (err) {
|
|
43
|
-
return [err, void 0];
|
|
39
|
+
if (isPromiseLike(value)) {
|
|
40
|
+
return Promise.resolve(value).then((value2) => success(value2)).catch((err) => failure(err));
|
|
44
41
|
}
|
|
45
|
-
}
|
|
46
|
-
async function goExpect(value, error) {
|
|
47
|
-
const _error = error ?? ((e) => e);
|
|
48
42
|
try {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
const [err, res] = await goTry(unwrappedValue);
|
|
52
|
-
if (err !== void 0) {
|
|
53
|
-
throw new Error(err);
|
|
54
|
-
}
|
|
55
|
-
return res;
|
|
56
|
-
}
|
|
57
|
-
return unwrappedValue;
|
|
43
|
+
const result = typeof value === "function" ? value() : value;
|
|
44
|
+
return success(result);
|
|
58
45
|
} catch (err) {
|
|
59
|
-
|
|
46
|
+
return failure(err);
|
|
60
47
|
}
|
|
61
48
|
}
|
|
62
49
|
|
|
63
|
-
export {
|
|
50
|
+
export { failure, goTry, goTryRaw, isFailure, isSuccess, success };
|
package/package.json
CHANGED
|
@@ -1,59 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"np": "^7.6.1",
|
|
52
|
-
"ts-jest": "^28.0.4",
|
|
53
|
-
"ts-node": "^10.8.2",
|
|
54
|
-
"typescript": "^5.4.5"
|
|
55
|
-
},
|
|
56
|
-
"dependencies": {
|
|
57
|
-
"p-is-promise": "^4.0.0"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
2
|
+
"name": "go-go-try",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "Tries to execute a sync/async function, returns a result tuple",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": "thelinuxlich/go-go-try",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Alisson Cavalcante Agiani",
|
|
9
|
+
"email": "thelinuxlich@gmail.com"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.mjs",
|
|
14
|
+
"types": "./dist/index.d.mts",
|
|
15
|
+
"exports": {
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
},
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/index.d.mts",
|
|
22
|
+
"default": "./dist/index.mjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=16"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "pkgroll",
|
|
30
|
+
"lint": "biome lint --write src/*.ts",
|
|
31
|
+
"test": "npm run build && npm run lint && tsx test.ts"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"errors",
|
|
35
|
+
"try",
|
|
36
|
+
"catch",
|
|
37
|
+
"error handling",
|
|
38
|
+
"nice-try",
|
|
39
|
+
"good-try",
|
|
40
|
+
"go-go-try",
|
|
41
|
+
"gotry",
|
|
42
|
+
"go-try"
|
|
43
|
+
],
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@biomejs/biome": "^1.9.4",
|
|
46
|
+
"pkgroll": "^2.12.1",
|
|
47
|
+
"tsx": "^4.19.3",
|
|
48
|
+
"typescript": "^5.8.3"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,102 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
type ResultTuple<T> = readonly [undefined, T] | readonly [string, undefined]
|
|
3
|
-
type RawResultTuple<T, E = unknown> =
|
|
4
|
-
| readonly [undefined, T]
|
|
5
|
-
| readonly [E, undefined]
|
|
1
|
+
export type Success<T> = readonly [undefined, T]
|
|
6
2
|
|
|
7
|
-
type
|
|
8
|
-
|
|
3
|
+
export type Failure<E> = readonly [E, undefined]
|
|
4
|
+
|
|
5
|
+
export type Result<E, T> = Success<T> | Failure<E>
|
|
6
|
+
|
|
7
|
+
export function isSuccess<E, T>(result: Result<E, T>): result is Success<T> {
|
|
8
|
+
return result[0] === undefined
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
typeof error === 'object' &&
|
|
14
|
-
error !== null &&
|
|
15
|
-
'message' in error &&
|
|
16
|
-
typeof (error as Record<string, unknown>).message === 'string'
|
|
17
|
-
)
|
|
11
|
+
export function isFailure<E, T>(result: Result<E, T>): result is Failure<E> {
|
|
12
|
+
return result[0] !== undefined
|
|
18
13
|
}
|
|
19
14
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
15
|
+
export function success<T>(value: T): Success<T> {
|
|
16
|
+
return [undefined, value] as const
|
|
17
|
+
}
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} catch {
|
|
28
|
-
// fallback in case there's an error stringifying the maybeError
|
|
29
|
-
// with circular references for example.
|
|
30
|
-
return new Error(String(maybeError))
|
|
31
|
-
}
|
|
19
|
+
export function failure<E>(error: E): Failure<E> {
|
|
20
|
+
return [error, undefined] as const
|
|
32
21
|
}
|
|
33
22
|
|
|
34
23
|
function getErrorMessage(error: unknown): string {
|
|
35
|
-
|
|
36
|
-
}
|
|
24
|
+
if (typeof error === 'string') return error
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
if (
|
|
27
|
+
typeof error === 'object' &&
|
|
28
|
+
error !== null &&
|
|
29
|
+
'message' in error &&
|
|
30
|
+
typeof (error as Record<string, unknown>).message === 'string'
|
|
31
|
+
) {
|
|
32
|
+
return (error as { message: string }).message
|
|
33
|
+
}
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let unwrappedValue: T | PromiseLike<T>
|
|
48
|
-
try {
|
|
49
|
-
unwrappedValue = typeof value === 'function' ? value() : value
|
|
50
|
-
if (isPromise(unwrappedValue)) {
|
|
51
|
-
return Promise.resolve(unwrappedValue)
|
|
52
|
-
.then((value) => [undefined, value] as const)
|
|
53
|
-
.catch((err) => [getErrorMessage(err), undefined] as const)
|
|
54
|
-
}
|
|
55
|
-
return [undefined, unwrappedValue] as const
|
|
56
|
-
} catch (err) {
|
|
57
|
-
return [getErrorMessage(err), undefined] as const
|
|
58
|
-
}
|
|
35
|
+
try {
|
|
36
|
+
return JSON.stringify(error)
|
|
37
|
+
} catch {
|
|
38
|
+
return String(error)
|
|
39
|
+
}
|
|
59
40
|
}
|
|
60
41
|
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
let unwrappedValue: T | PromiseLike<T>
|
|
69
|
-
try {
|
|
70
|
-
unwrappedValue = typeof value === 'function' ? value() : value
|
|
71
|
-
if (isPromise(unwrappedValue)) {
|
|
72
|
-
return Promise.resolve(unwrappedValue)
|
|
73
|
-
.then((value) => [undefined, value] as const)
|
|
74
|
-
.catch((err) => [err, undefined] as const)
|
|
75
|
-
}
|
|
76
|
-
return [undefined, unwrappedValue] as const
|
|
77
|
-
} catch (err) {
|
|
78
|
-
return [err, undefined] as const as RawResultTuple<T, E>
|
|
79
|
-
}
|
|
42
|
+
function isPromiseLike<T>(value: unknown): value is PromiseLike<T> {
|
|
43
|
+
return (
|
|
44
|
+
typeof value === 'object' &&
|
|
45
|
+
value !== null &&
|
|
46
|
+
'then' in value &&
|
|
47
|
+
typeof (value as { then: unknown }).then === 'function'
|
|
48
|
+
)
|
|
80
49
|
}
|
|
50
|
+
export function goTry<T>(fn: () => T): Result<string, T>
|
|
51
|
+
export function goTry<T>(value: T): Result<string, T>
|
|
52
|
+
export function goTry<T>(promise: PromiseLike<T>): Promise<Result<string, T>>
|
|
53
|
+
|
|
54
|
+
export function goTry<T>(
|
|
55
|
+
value: T | (() => T) | PromiseLike<T>,
|
|
56
|
+
): Result<string, T> | Promise<Result<string, T>> {
|
|
57
|
+
if (isPromiseLike<T>(value)) {
|
|
58
|
+
return Promise.resolve(value)
|
|
59
|
+
.then((value) => success(value))
|
|
60
|
+
.catch((err) => failure(getErrorMessage(err)))
|
|
61
|
+
}
|
|
81
62
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const unwrappedValue = typeof value === 'function' ? value() : value
|
|
89
|
-
if (isPromise(unwrappedValue)) {
|
|
90
|
-
const [err, res] = await goTry(unwrappedValue)
|
|
91
|
-
if (err !== undefined) {
|
|
92
|
-
throw new Error(err)
|
|
93
|
-
}
|
|
94
|
-
return res
|
|
95
|
-
}
|
|
96
|
-
return unwrappedValue
|
|
97
|
-
} catch (err) {
|
|
98
|
-
throw new Error(_error(getErrorMessage(err)))
|
|
99
|
-
}
|
|
63
|
+
try {
|
|
64
|
+
const result = typeof value === 'function' ? (value as () => T)() : value
|
|
65
|
+
return success(result)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return failure(getErrorMessage(err))
|
|
68
|
+
}
|
|
100
69
|
}
|
|
70
|
+
export function goTryRaw<E = unknown, T = unknown>(fn: () => T): Result<E, T>
|
|
71
|
+
export function goTryRaw<E = unknown, T = unknown>(value: T): Result<E, T>
|
|
72
|
+
export function goTryRaw<E = unknown, T = unknown>(
|
|
73
|
+
promise: PromiseLike<T>,
|
|
74
|
+
): Promise<Result<E, T>>
|
|
101
75
|
|
|
102
|
-
export
|
|
76
|
+
export function goTryRaw<E = unknown, T = unknown>(
|
|
77
|
+
value: T | (() => T) | PromiseLike<T>,
|
|
78
|
+
): Result<E, T> | Promise<Result<E, T>> {
|
|
79
|
+
if (isPromiseLike<T>(value)) {
|
|
80
|
+
return Promise.resolve(value)
|
|
81
|
+
.then((value) => success<T>(value))
|
|
82
|
+
.catch((err) => failure<E>(err as E))
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const result = typeof value === 'function' ? (value as () => T)() : value
|
|
86
|
+
return success<T>(result)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
return failure<E>(err as E)
|
|
89
|
+
}
|
|
90
|
+
}
|
package/test.ts
CHANGED
|
@@ -1,87 +1,119 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
1
|
+
import * as assert from 'node:assert'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { goTry, goTryRaw, isFailure, isSuccess } from './src/index.js'
|
|
4
|
+
|
|
5
|
+
test(`value returned by callback is used when callback doesn't throw`, async () => {
|
|
6
|
+
const fn = () => 'value'
|
|
7
|
+
|
|
8
|
+
// Test with function
|
|
9
|
+
const result1 = goTry(fn)
|
|
10
|
+
const result2 = goTryRaw(fn)
|
|
11
|
+
|
|
12
|
+
// Test destructuring
|
|
13
|
+
const [err1, value] = result1
|
|
14
|
+
const [err2, value2] = result2
|
|
15
|
+
|
|
16
|
+
// Test type guards
|
|
17
|
+
assert.equal(isSuccess(result1), true)
|
|
18
|
+
assert.equal(isSuccess(result2), true)
|
|
19
|
+
assert.equal(isFailure(result1), false)
|
|
20
|
+
assert.equal(isFailure(result2), false)
|
|
21
|
+
|
|
22
|
+
// Test values
|
|
23
|
+
assert.equal(value, 'value')
|
|
24
|
+
assert.equal(value2, 'value')
|
|
25
|
+
assert.equal(err1, undefined)
|
|
26
|
+
assert.equal(err2, undefined)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('if callback throws, value should be undefined and err should contain the error message', async () => {
|
|
30
|
+
const fn = () => {
|
|
31
|
+
return JSON.parse('{/') as string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Test with function that throws
|
|
35
|
+
const result1 = goTry(fn)
|
|
36
|
+
const result2 = goTryRaw<Error>(fn)
|
|
37
|
+
|
|
38
|
+
// Test destructuring
|
|
39
|
+
const [err1, value] = result1
|
|
40
|
+
const [err2, value2] = result2
|
|
41
|
+
|
|
42
|
+
// Test type guards
|
|
43
|
+
assert.equal(isSuccess(result1), false)
|
|
44
|
+
assert.equal(isSuccess(result2), false)
|
|
45
|
+
assert.equal(isFailure(result1), true)
|
|
46
|
+
assert.equal(isFailure(result2), true)
|
|
47
|
+
|
|
48
|
+
// Test values
|
|
49
|
+
assert.equal(value, undefined)
|
|
50
|
+
assert.equal(value2, undefined)
|
|
51
|
+
assert.equal(typeof err1, 'string')
|
|
52
|
+
assert.equal(typeof err2, 'object')
|
|
53
|
+
assert.equal(typeof err2?.message, 'string')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('first parameter accepts promises and makes the function async', async () => {
|
|
57
|
+
const promise = Promise.resolve('value')
|
|
58
|
+
|
|
59
|
+
// Test with promise
|
|
60
|
+
const result1 = await goTry(promise)
|
|
61
|
+
const result2 = await goTryRaw(promise)
|
|
62
|
+
|
|
63
|
+
// Test destructuring
|
|
64
|
+
const [err1, value] = result1
|
|
65
|
+
const [err2, value2] = result2
|
|
66
|
+
|
|
67
|
+
// Test type guards
|
|
68
|
+
assert.equal(isSuccess(result1), true)
|
|
69
|
+
assert.equal(isSuccess(result2), true)
|
|
70
|
+
assert.equal(isFailure(result1), false)
|
|
71
|
+
assert.equal(isFailure(result2), false)
|
|
72
|
+
|
|
73
|
+
// Test values
|
|
74
|
+
assert.equal(value, 'value')
|
|
75
|
+
assert.equal(value2, 'value')
|
|
76
|
+
assert.equal(err1, undefined)
|
|
77
|
+
assert.equal(err2, undefined)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('if async callback throws, value should be undefined and err should contain the error message', async () => {
|
|
81
|
+
const promise = Promise.reject(new Error('error'))
|
|
82
|
+
|
|
83
|
+
// Test with promise that rejects
|
|
84
|
+
const result1 = await goTry(promise)
|
|
85
|
+
const result2 = await goTryRaw<Error>(promise)
|
|
86
|
+
|
|
87
|
+
// Test destructuring
|
|
88
|
+
const [err, value] = result1
|
|
89
|
+
const [err2, value2] = result2
|
|
90
|
+
|
|
91
|
+
// Test type guards
|
|
92
|
+
assert.equal(isSuccess(result1), false)
|
|
93
|
+
assert.equal(isSuccess(result2), false)
|
|
94
|
+
assert.equal(isFailure(result1), true)
|
|
95
|
+
assert.equal(isFailure(result2), true)
|
|
96
|
+
|
|
97
|
+
// Test values
|
|
98
|
+
assert.equal(value, undefined)
|
|
99
|
+
assert.equal(value2, undefined)
|
|
100
|
+
assert.equal(err, 'error')
|
|
101
|
+
assert.equal(typeof err2, 'object')
|
|
102
|
+
assert.equal(err2?.message, 'error')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('direct values work too', () => {
|
|
106
|
+
// Test with direct value
|
|
107
|
+
const result1 = goTry('value')
|
|
108
|
+
const result2 = goTryRaw('value')
|
|
109
|
+
|
|
110
|
+
// Test destructuring
|
|
111
|
+
const [err1, value] = result1
|
|
112
|
+
const [err2, value2] = result2
|
|
113
|
+
|
|
114
|
+
// Test values
|
|
115
|
+
assert.equal(value, 'value')
|
|
116
|
+
assert.equal(value2, 'value')
|
|
117
|
+
assert.equal(err1, undefined)
|
|
118
|
+
assert.equal(err2, undefined)
|
|
87
119
|
})
|
package/tsconfig.json
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2020",
|
|
4
|
-
// previously "CommonJS", we now use "ES2015" to transpile TypeScript to "export default"
|
|
5
|
-
// instead of "exports.default ="
|
|
6
4
|
"module": "ES2015",
|
|
7
5
|
"esModuleInterop": true,
|
|
8
|
-
// From what I understand while reading the docs "classic" was introduced first and "classic" is by
|
|
9
|
-
// default(except when "module": "commonjs") because of backwards compatibility.
|
|
10
|
-
//
|
|
11
|
-
// From the docs: "node module resolution is the most-commonly used in the TypeScript community and is
|
|
12
|
-
// recommended for most projects."
|
|
13
6
|
"moduleResolution": "node",
|
|
14
|
-
// the npm package include ".js" files only so we need ".d.ts" files to be generated
|
|
15
7
|
"declaration": true,
|
|
16
|
-
// - ℹ️ https://www.typescriptlang.org/tsconfig#isolatedModules
|
|
17
|
-
// - 😃 Deno requires "isolatedModules" to be set to `true`
|
|
18
|
-
// - 😃 the stricter version - I always prefer the stricter version
|
|
19
|
-
// - 😃 probably the future
|
|
20
8
|
"isolatedModules": true,
|
|
21
|
-
// strict options ensuring more stable code
|
|
22
9
|
"strict": true,
|
|
23
10
|
"noUnusedLocals": true,
|
|
24
11
|
"noUnusedParameters": true,
|
|
@@ -26,8 +13,5 @@
|
|
|
26
13
|
"noUncheckedIndexedAccess": true,
|
|
27
14
|
"noFallthroughCasesInSwitch": true,
|
|
28
15
|
"forceConsistentCasingInFileNames": true
|
|
29
|
-
}
|
|
30
|
-
"include": [
|
|
31
|
-
"src/index.ts"
|
|
32
|
-
]
|
|
16
|
+
}
|
|
33
17
|
}
|
package/jest.config.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { Config } from '@jest/types'
|
|
2
|
-
|
|
3
|
-
const config: Config.InitialOptions = {
|
|
4
|
-
// adds support for tests written in TypeScript
|
|
5
|
-
preset: 'ts-jest',
|
|
6
|
-
|
|
7
|
-
// adds jsdom (https://github.com/jsdom/jsdom) APIs to tests
|
|
8
|
-
testEnvironment: 'jsdom',
|
|
9
|
-
|
|
10
|
-
// run tests on root and in /test folder with .ts and .tsx extensions
|
|
11
|
-
testMatch: ['<rootDir>/test.ts?(x)', '<rootDir>/test/**.ts?(x)'],
|
|
12
|
-
|
|
13
|
-
// ESM support
|
|
14
|
-
// https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/
|
|
15
|
-
globals: {
|
|
16
|
-
'ts-jest': {
|
|
17
|
-
useESM: true,
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
|
21
|
-
|
|
22
|
-
// restores original implementation (after each test is finished) when calling `jest.spyOn()`
|
|
23
|
-
// inside of a test:
|
|
24
|
-
// - https://jestjs.io/docs/configuration#restoremocks-boolean
|
|
25
|
-
// - https://jestjs.io/docs/mock-function-api#mockfnmockrestore
|
|
26
|
-
restoreMocks: true,
|
|
27
|
-
|
|
28
|
-
// 🐛 fix "having test.js and test.ts runs the test.js file"
|
|
29
|
-
// what about: ts-node CLI option:
|
|
30
|
-
// --prefer-ts-exts Re-order file extensions so that TypeScript imports are preferred (TS_NODE_PREFER_TS_EXTS, default: false)
|
|
31
|
-
moduleNameMapper: {
|
|
32
|
-
'^(\\..*)$': ['$1/index.ts', '$1.ts', '$1'],
|
|
33
|
-
},
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default config
|