go-go-try 7.3.0 → 7.4.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 +84 -18
- package/dist/index.cjs +36 -16
- package/dist/index.d.cts +66 -17
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +66 -17
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +36 -17
- package/package.json +3 -3
- package/src/goTryRaw.ts +64 -27
- package/src/index.test.ts +233 -24
- package/src/index.ts +4 -0
- package/src/types.ts +14 -0
- package/src/unknown-error.ts +20 -0
package/README.md
CHANGED
|
@@ -46,10 +46,10 @@ const [err, todos = []] = await goTry(fetchTodos()) // err is string | undefined
|
|
|
46
46
|
if (err) sendToErrorTrackingService(err)
|
|
47
47
|
|
|
48
48
|
// goTry extracts the error message from the error object, if you want the raw error object, use goTryRaw
|
|
49
|
-
const [err, value] = goTryRaw(() => JSON.parse('{/}')) // err is
|
|
49
|
+
const [err, value] = goTryRaw(() => JSON.parse('{/}')) // err is UnknownError | undefined, value is T | undefined
|
|
50
50
|
|
|
51
51
|
// fetch todos, fallback to empty array, send error to your error tracking service
|
|
52
|
-
const [err, todos = []] = await goTryRaw(fetchTodos()) // err is
|
|
52
|
+
const [err, todos = []] = await goTryRaw(fetchTodos()) // err is UnknownError | undefined
|
|
53
53
|
if (err) sendToErrorTrackingService(err)
|
|
54
54
|
```
|
|
55
55
|
|
|
@@ -233,10 +233,10 @@ type AppErrorVerbose =
|
|
|
233
233
|
|
|
234
234
|
// Use in functions with typed error returns
|
|
235
235
|
async function fetchUser(id: string): Promise<Result<AppError, User>> {
|
|
236
|
-
const [dbErr, user] = await goTryRaw(queryDatabase(id), DatabaseError)
|
|
236
|
+
const [dbErr, user] = await goTryRaw(queryDatabase(id), { errorClass: DatabaseError })
|
|
237
237
|
if (dbErr) return failure(dbErr)
|
|
238
238
|
|
|
239
|
-
const [netErr, enriched] = await goTryRaw(enrichUserData(user!), NetworkError)
|
|
239
|
+
const [netErr, enriched] = await goTryRaw(enrichUserData(user!), { errorClass: NetworkError })
|
|
240
240
|
if (netErr) return failure(netErr)
|
|
241
241
|
|
|
242
242
|
return [undefined, enriched] as const
|
|
@@ -318,32 +318,51 @@ Executes a function, promise, or value and returns a Result type with error mess
|
|
|
318
318
|
function goTry<T>(value: T | Promise<T> | (() => T | Promise<T>)): Result<string, T> | Promise<Result<string, T>>
|
|
319
319
|
```
|
|
320
320
|
|
|
321
|
-
### `goTryRaw<T, E>(value,
|
|
321
|
+
### `goTryRaw<T, E>(value, options?)`
|
|
322
322
|
|
|
323
323
|
Like `goTry` but returns the raw Error object instead of just the message.
|
|
324
324
|
|
|
325
|
-
|
|
325
|
+
By default, errors are wrapped in `UnknownError` (a tagged error class). You can customize this behavior with the options object:
|
|
326
326
|
|
|
327
327
|
```ts
|
|
328
|
-
|
|
329
|
-
|
|
328
|
+
type GoTryRawOptions<E> =
|
|
329
|
+
| { errorClass: ErrorConstructor<E> } // Wrap ALL errors in this class
|
|
330
|
+
| { systemErrorClass: ErrorConstructor<E> } // Only wrap non-tagged errors
|
|
331
|
+
| {} // Use defaults
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
> **Note:** `errorClass` and `systemErrorClass` are **mutually exclusive**. TypeScript will show an error if you try to pass both.
|
|
335
|
+
> - Use `errorClass` when you want all errors wrapped in a specific type
|
|
336
|
+
> - Use `systemErrorClass` when you want tagged errors to pass through and only wrap untagged errors
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
// Without options - err is UnknownError | undefined
|
|
340
|
+
function goTryRaw<T>(value: T | Promise<T> | (() => T | Promise<T>)): Result<UnknownError, T> | Promise<Result<UnknownError, T>>
|
|
330
341
|
|
|
331
|
-
// With
|
|
332
|
-
function goTryRaw<T, E>(value: T | Promise<T> | (() => T | Promise<T>),
|
|
342
|
+
// With options object - err is E | undefined
|
|
343
|
+
function goTryRaw<T, E>(value: T | Promise<T> | (() => T | Promise<T>), options: GoTryRawOptions<E>): Result<E, T> | Promise<Result<E, T>>
|
|
333
344
|
```
|
|
334
345
|
|
|
335
|
-
**
|
|
346
|
+
**Examples:**
|
|
347
|
+
|
|
336
348
|
```ts
|
|
337
349
|
const DatabaseError = taggedError('DatabaseError')
|
|
350
|
+
const NetworkError = taggedError('NetworkError')
|
|
338
351
|
|
|
339
|
-
//
|
|
352
|
+
// Default - errors wrapped in UnknownError
|
|
340
353
|
const [err1, data1] = await goTryRaw(fetchData())
|
|
341
|
-
// err1 is
|
|
354
|
+
// err1 is UnknownError | undefined
|
|
355
|
+
// err1?._tag === 'UnknownError'
|
|
342
356
|
|
|
343
|
-
//
|
|
344
|
-
const [err2, data2] = await goTryRaw(fetchData(), DatabaseError)
|
|
357
|
+
// Options object - wrap all errors
|
|
358
|
+
const [err2, data2] = await goTryRaw(fetchData(), { errorClass: DatabaseError })
|
|
345
359
|
// err2 is DatabaseError | undefined
|
|
346
|
-
// err2
|
|
360
|
+
// err2?._tag === 'DatabaseError'
|
|
361
|
+
|
|
362
|
+
// Options object - systemErrorClass only wraps non-tagged errors
|
|
363
|
+
const [err3, data3] = await goTryRaw(fetchData(), { systemErrorClass: NetworkError })
|
|
364
|
+
// If fetchData throws a tagged error (e.g., DatabaseError), it passes through
|
|
365
|
+
// If fetchData throws a plain Error, it gets wrapped in NetworkError
|
|
347
366
|
```
|
|
348
367
|
|
|
349
368
|
### `goTryAll<T>(items, options?)`
|
|
@@ -453,6 +472,47 @@ console.log(err.name) // 'DatabaseError'
|
|
|
453
472
|
console.log(err.cause) // originalError
|
|
454
473
|
```
|
|
455
474
|
|
|
475
|
+
### `UnknownError`
|
|
476
|
+
|
|
477
|
+
A default tagged error class used by `goTryRaw` when no options are provided, or when `systemErrorClass` is not specified in the options object.
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
import { UnknownError, goTryRaw } from 'go-go-try'
|
|
481
|
+
|
|
482
|
+
// Errors are automatically wrapped in UnknownError
|
|
483
|
+
const [err, data] = goTryRaw(() => {
|
|
484
|
+
throw new Error('something went wrong')
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
if (err) {
|
|
488
|
+
console.log(err._tag) // 'UnknownError'
|
|
489
|
+
console.log(err.message) // 'something went wrong'
|
|
490
|
+
console.log(err.cause) // original Error
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Since `UnknownError` is the default for `systemErrorClass`, you can distinguish between known tagged errors and unexpected system errors without specifying any options:
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
const DatabaseError = taggedError('DatabaseError')
|
|
498
|
+
|
|
499
|
+
function fetchData() {
|
|
500
|
+
// Operations that might throw DatabaseError should use errorClass to wrap them
|
|
501
|
+
const [err1, data1] = goTryRaw(() => queryDatabase(), { errorClass: DatabaseError })
|
|
502
|
+
|
|
503
|
+
// Other operations use the default behavior - non-tagged errors become UnknownError
|
|
504
|
+
const [err2, data2] = goTryRaw(() => parseData(data1))
|
|
505
|
+
// err2 is UnknownError | undefined
|
|
506
|
+
|
|
507
|
+
// Now you can distinguish between known and unknown error types
|
|
508
|
+
if (err1) {
|
|
509
|
+
console.log('Known DB error:', err1.message)
|
|
510
|
+
} else if (err2) {
|
|
511
|
+
console.log('Unexpected error:', err2.message)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
456
516
|
### `TaggedUnion<T>`
|
|
457
517
|
|
|
458
518
|
Creates a union type from multiple tagged error classes.
|
|
@@ -487,11 +547,11 @@ When using `goTryRaw` with different error classes in the same function, TypeScr
|
|
|
487
547
|
// No explicit return type needed!
|
|
488
548
|
async function fetchUserData(id: string) {
|
|
489
549
|
// First operation might fail with DatabaseError
|
|
490
|
-
const [dbErr, user] = await goTryRaw(queryDb(id), DatabaseError)
|
|
550
|
+
const [dbErr, user] = await goTryRaw(queryDb(id), { errorClass: DatabaseError })
|
|
491
551
|
if (dbErr) return failure(dbErr) // returns Failure<DatabaseError>
|
|
492
552
|
|
|
493
553
|
// Second operation might fail with NetworkError
|
|
494
|
-
const [netErr, enriched] = await goTryRaw(enrichUser(user!), NetworkError)
|
|
554
|
+
const [netErr, enriched] = await goTryRaw(enrichUser(user!), { errorClass: NetworkError })
|
|
495
555
|
if (netErr) return failure(netErr) // returns Failure<NetworkError>
|
|
496
556
|
|
|
497
557
|
return success(enriched) // returns Success<User>
|
|
@@ -525,6 +585,12 @@ type Result<E, T> = Success<T> | Failure<E>
|
|
|
525
585
|
type TaggedInstance<T> = T extends ErrorConstructor<infer E> ? E : never
|
|
526
586
|
type TaggedUnion<T extends readonly ErrorConstructor<unknown>[]> =
|
|
527
587
|
{ [K in keyof T]: T[K] extends ErrorConstructor<infer E> ? E : never }[number]
|
|
588
|
+
|
|
589
|
+
// Options for goTryRaw (errorClass and systemErrorClass are mutually exclusive)
|
|
590
|
+
type GoTryRawOptions<E> =
|
|
591
|
+
| { errorClass: ErrorConstructor<E>; systemErrorClass?: never }
|
|
592
|
+
| { errorClass?: never; systemErrorClass: ErrorConstructor<E> }
|
|
593
|
+
| { errorClass?: never; systemErrorClass?: never }
|
|
528
594
|
```
|
|
529
595
|
|
|
530
596
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -50,16 +50,46 @@ function goTry(value) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function
|
|
53
|
+
function taggedError(tag) {
|
|
54
|
+
return class TaggedErrorClass extends Error {
|
|
55
|
+
constructor(message, options) {
|
|
56
|
+
super(message);
|
|
57
|
+
this._tag = tag;
|
|
58
|
+
this.name = tag;
|
|
59
|
+
this.cause = options?.cause;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const UnknownError = taggedError("UnknownError");
|
|
65
|
+
|
|
66
|
+
function isTaggedError(err) {
|
|
67
|
+
return isError(err) && "_tag" in err && typeof err._tag === "string";
|
|
68
|
+
}
|
|
69
|
+
function goTryRaw(value, options) {
|
|
70
|
+
const { errorClass, systemErrorClass } = options || {};
|
|
71
|
+
const actualSystemErrorClass = systemErrorClass ?? UnknownError;
|
|
54
72
|
const wrapError = (err) => {
|
|
55
|
-
if (
|
|
73
|
+
if (errorClass) {
|
|
74
|
+
if (err === void 0) {
|
|
75
|
+
return new errorClass("undefined");
|
|
76
|
+
}
|
|
77
|
+
if (isError(err)) {
|
|
78
|
+
return new errorClass(err.message, { cause: err });
|
|
79
|
+
}
|
|
80
|
+
return new errorClass(String(err));
|
|
81
|
+
}
|
|
82
|
+
if (actualSystemErrorClass) {
|
|
83
|
+
if (isTaggedError(err)) {
|
|
84
|
+
return err;
|
|
85
|
+
}
|
|
56
86
|
if (err === void 0) {
|
|
57
|
-
return new
|
|
87
|
+
return new actualSystemErrorClass("undefined");
|
|
58
88
|
}
|
|
59
89
|
if (isError(err)) {
|
|
60
|
-
return new
|
|
90
|
+
return new actualSystemErrorClass(err.message, { cause: err });
|
|
61
91
|
}
|
|
62
|
-
return new
|
|
92
|
+
return new actualSystemErrorClass(String(err));
|
|
63
93
|
}
|
|
64
94
|
if (err === void 0) {
|
|
65
95
|
return new Error("undefined");
|
|
@@ -153,17 +183,6 @@ async function goTryAllRaw(items, options) {
|
|
|
153
183
|
return [errors, results];
|
|
154
184
|
}
|
|
155
185
|
|
|
156
|
-
function taggedError(tag) {
|
|
157
|
-
return class TaggedErrorClass extends Error {
|
|
158
|
-
constructor(message, options) {
|
|
159
|
-
super(message);
|
|
160
|
-
this._tag = tag;
|
|
161
|
-
this.name = tag;
|
|
162
|
-
this.cause = options?.cause;
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
186
|
function assert(condition, errorOrClass, message) {
|
|
168
187
|
if (!condition) {
|
|
169
188
|
if (typeof errorOrClass === "string") {
|
|
@@ -176,6 +195,7 @@ function assert(condition, errorOrClass, message) {
|
|
|
176
195
|
}
|
|
177
196
|
}
|
|
178
197
|
|
|
198
|
+
exports.UnknownError = UnknownError;
|
|
179
199
|
exports.assert = assert;
|
|
180
200
|
exports.assertNever = assertNever;
|
|
181
201
|
exports.failure = failure;
|
package/dist/index.d.cts
CHANGED
|
@@ -28,6 +28,20 @@ interface GoTryAllOptions {
|
|
|
28
28
|
type ErrorConstructor<E> = new (message: string, options?: {
|
|
29
29
|
cause?: unknown;
|
|
30
30
|
}) => E;
|
|
31
|
+
/**
|
|
32
|
+
* Options for goTryRaw function.
|
|
33
|
+
* errorClass and systemErrorClass are mutually exclusive - you can only provide one.
|
|
34
|
+
*/
|
|
35
|
+
type GoTryRawOptions<E = Error> = {
|
|
36
|
+
errorClass: ErrorConstructor<E>;
|
|
37
|
+
systemErrorClass?: never;
|
|
38
|
+
} | {
|
|
39
|
+
errorClass?: never;
|
|
40
|
+
systemErrorClass: ErrorConstructor<E>;
|
|
41
|
+
} | {
|
|
42
|
+
errorClass?: never;
|
|
43
|
+
systemErrorClass?: never;
|
|
44
|
+
};
|
|
31
45
|
/**
|
|
32
46
|
* Creates a union type from multiple tagged error classes.
|
|
33
47
|
*
|
|
@@ -71,14 +85,44 @@ declare function goTry<T>(promise: Promise<T>): Promise<Result<string, T>>;
|
|
|
71
85
|
declare function goTry<T>(fn: () => T): Result<string, T>;
|
|
72
86
|
declare function goTry<T>(value: T): Result<string, T>;
|
|
73
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Default system error class for errors that aren't already wrapped in a tagged error class.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // By default, goTryRaw wraps unknown errors in UnknownError
|
|
93
|
+
* const [err, result] = goTryRaw(() => mightThrow())
|
|
94
|
+
* if (err) {
|
|
95
|
+
* console.log(err._tag) // 'UnknownError'
|
|
96
|
+
* }
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Use a custom system error class
|
|
100
|
+
* const SystemError = taggedError('SystemError')
|
|
101
|
+
* const [err, result] = goTryRaw(() => mightThrow(), {
|
|
102
|
+
* systemErrorClass: SystemError
|
|
103
|
+
* })
|
|
104
|
+
*/
|
|
105
|
+
declare const UnknownError: {
|
|
106
|
+
new (message: string, options?: {
|
|
107
|
+
cause?: unknown;
|
|
108
|
+
} | undefined): {
|
|
109
|
+
readonly _tag: "UnknownError";
|
|
110
|
+
readonly cause?: unknown;
|
|
111
|
+
name: string;
|
|
112
|
+
message: string;
|
|
113
|
+
stack?: string;
|
|
114
|
+
};
|
|
115
|
+
isError(error: unknown): error is Error;
|
|
116
|
+
};
|
|
117
|
+
|
|
74
118
|
/**
|
|
75
119
|
* Executes a function, promise, or value and returns a Result type.
|
|
76
120
|
* If an error occurs, it returns a Failure with the raw error object.
|
|
77
121
|
*
|
|
78
122
|
* @template T The type of the successful result
|
|
79
|
-
* @template E The type of the error
|
|
123
|
+
* @template E The type of the error
|
|
80
124
|
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
|
|
81
|
-
* @param {
|
|
125
|
+
* @param {GoTryRawOptions<E>} [options] - Optional options object
|
|
82
126
|
* @returns {Result<E, T> | Promise<Result<E, T>>} A Result type or a Promise of a Result type
|
|
83
127
|
*
|
|
84
128
|
* @example
|
|
@@ -94,21 +138,26 @@ declare function goTry<T>(value: T): Result<string, T>;
|
|
|
94
138
|
* const [err, result] = await goTryRaw(fetch('https://api.example.com/data'));
|
|
95
139
|
*
|
|
96
140
|
* @example
|
|
97
|
-
* // With
|
|
141
|
+
* // With options object - wrap all errors
|
|
98
142
|
* const DatabaseError = taggedError('DatabaseError');
|
|
99
|
-
* const [err, result] = await goTryRaw(fetchData(), DatabaseError);
|
|
100
|
-
*
|
|
143
|
+
* const [err, result] = await goTryRaw(fetchData(), { errorClass: DatabaseError });
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* // With options object - systemErrorClass only wraps non-tagged errors
|
|
147
|
+
* const [err, result] = await goTryRaw(fetchData(), { systemErrorClass: UnknownError });
|
|
148
|
+
* // Errors thrown as tagged errors pass through
|
|
149
|
+
* // Other errors are wrapped in UnknownError
|
|
101
150
|
*/
|
|
102
|
-
declare function goTryRaw<T
|
|
103
|
-
declare function goTryRaw<T, E =
|
|
104
|
-
declare function goTryRaw<T
|
|
105
|
-
declare function goTryRaw<T, E =
|
|
106
|
-
declare function goTryRaw<T
|
|
107
|
-
declare function goTryRaw<T, E =
|
|
108
|
-
declare function goTryRaw<T
|
|
109
|
-
declare function goTryRaw<T, E =
|
|
110
|
-
declare function goTryRaw<T
|
|
111
|
-
declare function goTryRaw<T, E =
|
|
151
|
+
declare function goTryRaw<T>(fn: () => never): Result<Error, never>;
|
|
152
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => never, options: GoTryRawOptions<E>): Result<E, never>;
|
|
153
|
+
declare function goTryRaw<T>(fn: () => Promise<T>): Promise<Result<Error, T>>;
|
|
154
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => Promise<T>, options: GoTryRawOptions<E>): Promise<Result<E, T>>;
|
|
155
|
+
declare function goTryRaw<T>(promise: Promise<T>): Promise<Result<Error, T>>;
|
|
156
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(promise: Promise<T>, options: GoTryRawOptions<E>): Promise<Result<E, T>>;
|
|
157
|
+
declare function goTryRaw<T>(fn: () => T): Result<Error, T>;
|
|
158
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => T, options: GoTryRawOptions<E>): Result<E, T>;
|
|
159
|
+
declare function goTryRaw<T>(value: T): Result<Error, T>;
|
|
160
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(value: T, options: GoTryRawOptions<E>): Result<E, T>;
|
|
112
161
|
|
|
113
162
|
/**
|
|
114
163
|
* Executes a function, promise, or value and returns a Result type with a fallback default.
|
|
@@ -301,6 +350,6 @@ declare function failure<E>(error: E): Failure<E>;
|
|
|
301
350
|
*/
|
|
302
351
|
declare function assertNever(value: never): never;
|
|
303
352
|
|
|
304
|
-
export { assert, assertNever, failure, goTry, goTryAll, goTryAllRaw, goTryOr, goTryRaw, isFailure, isSuccess, success, taggedError };
|
|
305
|
-
export type { ErrorConstructor, Failure, GoTryAllOptions, MaybePromise, Result, ResultWithDefault, Success, TaggedError, TaggedUnion };
|
|
353
|
+
export { UnknownError, assert, assertNever, failure, goTry, goTryAll, goTryAllRaw, goTryOr, goTryRaw, isFailure, isSuccess, success, taggedError };
|
|
354
|
+
export type { ErrorConstructor, Failure, GoTryAllOptions, GoTryRawOptions, MaybePromise, Result, ResultWithDefault, Success, TaggedError, TaggedUnion };
|
|
306
355
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sources":["../src/types.ts","../src/goTry.ts","../src/goTryRaw.ts","../src/goTryOr.ts","../src/goTryAll.ts","../src/tagged-error.ts","../src/assert.ts","../src/result-helpers.ts"],"mappings":"AAAA;;;AAIM,KAAM,OAAO;AACb,KAAM,OAAO;AACb,KAAM,MAAM,SAAS,OAAO,MAAM,OAAO;AAEzC,KAAM,iBAAiB;AAEvB,KAAM,YAAY,UAAU,OAAO;AAEzC;;;;AAIM,UAAW,WAAW;;;;;AAMtB,UAAW,eAAe;;;;;;;AAQhC;;;AAGM,KAAM,gBAAgB;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","sources":["../src/types.ts","../src/goTry.ts","../src/unknown-error.ts","../src/goTryRaw.ts","../src/goTryOr.ts","../src/goTryAll.ts","../src/tagged-error.ts","../src/assert.ts","../src/result-helpers.ts"],"mappings":"AAAA;;;AAIM,KAAM,OAAO;AACb,KAAM,OAAO;AACb,KAAM,MAAM,SAAS,OAAO,MAAM,OAAO;AAEzC,KAAM,iBAAiB;AAEvB,KAAM,YAAY,UAAU,OAAO;AAEzC;;;;AAIM,UAAW,WAAW;;;;;AAMtB,UAAW,eAAe;;;;;;;AAQhC;;;AAGM,KAAM,gBAAgB;;;AAO5B;;;;AAIM,KAAM,eAAe,KAAK,KAAK;gBACnB,gBAAgB;;;;sBACU,gBAAgB;;;;;AAG5D;;;;;;;;;;;;;AAaM,KAAM,WAAW,oBAAoB,gBAAgB;iCAC1B,gBAAgB;;;AC3DjD;;;;;;;;;;;;;;;;;;;;AAoBA,iBAAgB,KAAK,sBAAsB,MAAM;AACjD,iBAAgB,KAAK,cAAc,OAAO,MAAM,OAAO,CAAC,MAAM;AAC9D,iBAAgB,KAAK,aAAa,OAAO,MAAM,OAAO,CAAC,MAAM;AAC7D,iBAAgB,KAAK,kBAAkB,MAAM;AAC7C,iBAAgB,KAAK,eAAe,MAAM;;AC1B1C;;;;;;;;;;;;;;;;;AAiBA,cAAa,YAAY;;;;;;;;;;;;;ACPzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,iBAAgB,QAAQ,sBAAsB,MAAM,CAAC,KAAK;AAC1D,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,6BAA6B,eAAe,MAAM,MAAM;AACxH,iBAAgB,QAAQ,cACZ,OAAO,MAChB,OAAO,CAAC,MAAM,CAAC,KAAK;AACvB,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,aACpD,OAAO,cACR,eAAe,MACvB,OAAO,CAAC,MAAM;AACjB,iBAAgB,QAAQ,aACb,OAAO,MACf,OAAO,CAAC,MAAM,CAAC,KAAK;AACvB,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,YACrD,OAAO,cACP,eAAe,MACvB,OAAO,CAAC,MAAM;AACjB,iBAAgB,QAAQ,kBAAkB,MAAM,CAAC,KAAK;AACtD,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,yBAAyB,eAAe,MAAM,MAAM;AACpH,iBAAgB,QAAQ,eAAe,MAAM,CAAC,KAAK;AACnD,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,sBAAsB,eAAe,MAAM,MAAM;;AC5DjH;;;;;;;;;;;;;;;;;;;;;AAqBA,iBAAgB,OAAO,mDAAmD,iBAAiB;AAC3F,iBAAgB,OAAO,cACX,OAAO,mCAEhB,OAAO,CAAC,iBAAiB;AAC5B,iBAAgB,OAAO,aACZ,OAAO,mCAEf,OAAO,CAAC,iBAAiB;AAC5B,iBAAgB,OAAO,+CAA+C,iBAAiB;AACvF,iBAAgB,OAAO,4CAA4C,iBAAiB;;ACgBpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,iBAAsB,QAAQ;oBACH,OAAO,gBAAgB,OAAO;aAC7C,eAAe,GACxB,OAAO;;;;;AAoBV;;;;;;;;;;AAUA,iBAAsB,WAAW;oBACN,OAAO,gBAAgB,OAAO;aAC7C,eAAe,GACxB,OAAO;oBAAoB,KAAK;;;;;ACrHnC;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,iBAAgB,WAAW;;;;;;;;;;;;;AC1B3B;;;;;;;;;;;;;;;;;;;;;;AAsBA,iBAAgB,MAAM,4BAA4B,KAAK;AAEvD;;;;;;;;;;;;;;AAcA,iBAAgB,MAAM,WAAW,KAAK;;ACpCtC,iBAAgB,SAAS,eAAe,MAAM,mBAAmB,OAAO;AAIxE,iBAAgB,SAAS,eAAe,MAAM,mBAAmB,OAAO;AAIxE,iBAAgB,OAAO,eAAe,OAAO;AAI7C,iBAAgB,OAAO,eAAe,OAAO;AAI7C;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,iBAAgB,WAAW","names":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -28,6 +28,20 @@ interface GoTryAllOptions {
|
|
|
28
28
|
type ErrorConstructor<E> = new (message: string, options?: {
|
|
29
29
|
cause?: unknown;
|
|
30
30
|
}) => E;
|
|
31
|
+
/**
|
|
32
|
+
* Options for goTryRaw function.
|
|
33
|
+
* errorClass and systemErrorClass are mutually exclusive - you can only provide one.
|
|
34
|
+
*/
|
|
35
|
+
type GoTryRawOptions<E = Error> = {
|
|
36
|
+
errorClass: ErrorConstructor<E>;
|
|
37
|
+
systemErrorClass?: never;
|
|
38
|
+
} | {
|
|
39
|
+
errorClass?: never;
|
|
40
|
+
systemErrorClass: ErrorConstructor<E>;
|
|
41
|
+
} | {
|
|
42
|
+
errorClass?: never;
|
|
43
|
+
systemErrorClass?: never;
|
|
44
|
+
};
|
|
31
45
|
/**
|
|
32
46
|
* Creates a union type from multiple tagged error classes.
|
|
33
47
|
*
|
|
@@ -71,14 +85,44 @@ declare function goTry<T>(promise: Promise<T>): Promise<Result<string, T>>;
|
|
|
71
85
|
declare function goTry<T>(fn: () => T): Result<string, T>;
|
|
72
86
|
declare function goTry<T>(value: T): Result<string, T>;
|
|
73
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Default system error class for errors that aren't already wrapped in a tagged error class.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // By default, goTryRaw wraps unknown errors in UnknownError
|
|
93
|
+
* const [err, result] = goTryRaw(() => mightThrow())
|
|
94
|
+
* if (err) {
|
|
95
|
+
* console.log(err._tag) // 'UnknownError'
|
|
96
|
+
* }
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Use a custom system error class
|
|
100
|
+
* const SystemError = taggedError('SystemError')
|
|
101
|
+
* const [err, result] = goTryRaw(() => mightThrow(), {
|
|
102
|
+
* systemErrorClass: SystemError
|
|
103
|
+
* })
|
|
104
|
+
*/
|
|
105
|
+
declare const UnknownError: {
|
|
106
|
+
new (message: string, options?: {
|
|
107
|
+
cause?: unknown;
|
|
108
|
+
} | undefined): {
|
|
109
|
+
readonly _tag: "UnknownError";
|
|
110
|
+
readonly cause?: unknown;
|
|
111
|
+
name: string;
|
|
112
|
+
message: string;
|
|
113
|
+
stack?: string;
|
|
114
|
+
};
|
|
115
|
+
isError(error: unknown): error is Error;
|
|
116
|
+
};
|
|
117
|
+
|
|
74
118
|
/**
|
|
75
119
|
* Executes a function, promise, or value and returns a Result type.
|
|
76
120
|
* If an error occurs, it returns a Failure with the raw error object.
|
|
77
121
|
*
|
|
78
122
|
* @template T The type of the successful result
|
|
79
|
-
* @template E The type of the error
|
|
123
|
+
* @template E The type of the error
|
|
80
124
|
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
|
|
81
|
-
* @param {
|
|
125
|
+
* @param {GoTryRawOptions<E>} [options] - Optional options object
|
|
82
126
|
* @returns {Result<E, T> | Promise<Result<E, T>>} A Result type or a Promise of a Result type
|
|
83
127
|
*
|
|
84
128
|
* @example
|
|
@@ -94,21 +138,26 @@ declare function goTry<T>(value: T): Result<string, T>;
|
|
|
94
138
|
* const [err, result] = await goTryRaw(fetch('https://api.example.com/data'));
|
|
95
139
|
*
|
|
96
140
|
* @example
|
|
97
|
-
* // With
|
|
141
|
+
* // With options object - wrap all errors
|
|
98
142
|
* const DatabaseError = taggedError('DatabaseError');
|
|
99
|
-
* const [err, result] = await goTryRaw(fetchData(), DatabaseError);
|
|
100
|
-
*
|
|
143
|
+
* const [err, result] = await goTryRaw(fetchData(), { errorClass: DatabaseError });
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* // With options object - systemErrorClass only wraps non-tagged errors
|
|
147
|
+
* const [err, result] = await goTryRaw(fetchData(), { systemErrorClass: UnknownError });
|
|
148
|
+
* // Errors thrown as tagged errors pass through
|
|
149
|
+
* // Other errors are wrapped in UnknownError
|
|
101
150
|
*/
|
|
102
|
-
declare function goTryRaw<T
|
|
103
|
-
declare function goTryRaw<T, E =
|
|
104
|
-
declare function goTryRaw<T
|
|
105
|
-
declare function goTryRaw<T, E =
|
|
106
|
-
declare function goTryRaw<T
|
|
107
|
-
declare function goTryRaw<T, E =
|
|
108
|
-
declare function goTryRaw<T
|
|
109
|
-
declare function goTryRaw<T, E =
|
|
110
|
-
declare function goTryRaw<T
|
|
111
|
-
declare function goTryRaw<T, E =
|
|
151
|
+
declare function goTryRaw<T>(fn: () => never): Result<Error, never>;
|
|
152
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => never, options: GoTryRawOptions<E>): Result<E, never>;
|
|
153
|
+
declare function goTryRaw<T>(fn: () => Promise<T>): Promise<Result<Error, T>>;
|
|
154
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => Promise<T>, options: GoTryRawOptions<E>): Promise<Result<E, T>>;
|
|
155
|
+
declare function goTryRaw<T>(promise: Promise<T>): Promise<Result<Error, T>>;
|
|
156
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(promise: Promise<T>, options: GoTryRawOptions<E>): Promise<Result<E, T>>;
|
|
157
|
+
declare function goTryRaw<T>(fn: () => T): Result<Error, T>;
|
|
158
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => T, options: GoTryRawOptions<E>): Result<E, T>;
|
|
159
|
+
declare function goTryRaw<T>(value: T): Result<Error, T>;
|
|
160
|
+
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(value: T, options: GoTryRawOptions<E>): Result<E, T>;
|
|
112
161
|
|
|
113
162
|
/**
|
|
114
163
|
* Executes a function, promise, or value and returns a Result type with a fallback default.
|
|
@@ -301,6 +350,6 @@ declare function failure<E>(error: E): Failure<E>;
|
|
|
301
350
|
*/
|
|
302
351
|
declare function assertNever(value: never): never;
|
|
303
352
|
|
|
304
|
-
export { assert, assertNever, failure, goTry, goTryAll, goTryAllRaw, goTryOr, goTryRaw, isFailure, isSuccess, success, taggedError };
|
|
305
|
-
export type { ErrorConstructor, Failure, GoTryAllOptions, MaybePromise, Result, ResultWithDefault, Success, TaggedError, TaggedUnion };
|
|
353
|
+
export { UnknownError, assert, assertNever, failure, goTry, goTryAll, goTryAllRaw, goTryOr, goTryRaw, isFailure, isSuccess, success, taggedError };
|
|
354
|
+
export type { ErrorConstructor, Failure, GoTryAllOptions, GoTryRawOptions, MaybePromise, Result, ResultWithDefault, Success, TaggedError, TaggedUnion };
|
|
306
355
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sources":["../src/types.ts","../src/goTry.ts","../src/goTryRaw.ts","../src/goTryOr.ts","../src/goTryAll.ts","../src/tagged-error.ts","../src/assert.ts","../src/result-helpers.ts"],"mappings":"AAAA;;;AAIM,KAAM,OAAO;AACb,KAAM,OAAO;AACb,KAAM,MAAM,SAAS,OAAO,MAAM,OAAO;AAEzC,KAAM,iBAAiB;AAEvB,KAAM,YAAY,UAAU,OAAO;AAEzC;;;;AAIM,UAAW,WAAW;;;;;AAMtB,UAAW,eAAe;;;;;;;AAQhC;;;AAGM,KAAM,gBAAgB;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","sources":["../src/types.ts","../src/goTry.ts","../src/unknown-error.ts","../src/goTryRaw.ts","../src/goTryOr.ts","../src/goTryAll.ts","../src/tagged-error.ts","../src/assert.ts","../src/result-helpers.ts"],"mappings":"AAAA;;;AAIM,KAAM,OAAO;AACb,KAAM,OAAO;AACb,KAAM,MAAM,SAAS,OAAO,MAAM,OAAO;AAEzC,KAAM,iBAAiB;AAEvB,KAAM,YAAY,UAAU,OAAO;AAEzC;;;;AAIM,UAAW,WAAW;;;;;AAMtB,UAAW,eAAe;;;;;;;AAQhC;;;AAGM,KAAM,gBAAgB;;;AAO5B;;;;AAIM,KAAM,eAAe,KAAK,KAAK;gBACnB,gBAAgB;;;;sBACU,gBAAgB;;;;;AAG5D;;;;;;;;;;;;;AAaM,KAAM,WAAW,oBAAoB,gBAAgB;iCAC1B,gBAAgB;;;AC3DjD;;;;;;;;;;;;;;;;;;;;AAoBA,iBAAgB,KAAK,sBAAsB,MAAM;AACjD,iBAAgB,KAAK,cAAc,OAAO,MAAM,OAAO,CAAC,MAAM;AAC9D,iBAAgB,KAAK,aAAa,OAAO,MAAM,OAAO,CAAC,MAAM;AAC7D,iBAAgB,KAAK,kBAAkB,MAAM;AAC7C,iBAAgB,KAAK,eAAe,MAAM;;AC1B1C;;;;;;;;;;;;;;;;;AAiBA,cAAa,YAAY;;;;;;;;;;;;;ACPzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,iBAAgB,QAAQ,sBAAsB,MAAM,CAAC,KAAK;AAC1D,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,6BAA6B,eAAe,MAAM,MAAM;AACxH,iBAAgB,QAAQ,cACZ,OAAO,MAChB,OAAO,CAAC,MAAM,CAAC,KAAK;AACvB,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,aACpD,OAAO,cACR,eAAe,MACvB,OAAO,CAAC,MAAM;AACjB,iBAAgB,QAAQ,aACb,OAAO,MACf,OAAO,CAAC,MAAM,CAAC,KAAK;AACvB,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,YACrD,OAAO,cACP,eAAe,MACvB,OAAO,CAAC,MAAM;AACjB,iBAAgB,QAAQ,kBAAkB,MAAM,CAAC,KAAK;AACtD,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,yBAAyB,eAAe,MAAM,MAAM;AACpH,iBAAgB,QAAQ,eAAe,MAAM,CAAC,KAAK;AACnD,iBAAgB,QAAQ,QAAQ,YAAY,QAAQ,YAAY,sBAAsB,eAAe,MAAM,MAAM;;AC5DjH;;;;;;;;;;;;;;;;;;;;;AAqBA,iBAAgB,OAAO,mDAAmD,iBAAiB;AAC3F,iBAAgB,OAAO,cACX,OAAO,mCAEhB,OAAO,CAAC,iBAAiB;AAC5B,iBAAgB,OAAO,aACZ,OAAO,mCAEf,OAAO,CAAC,iBAAiB;AAC5B,iBAAgB,OAAO,+CAA+C,iBAAiB;AACvF,iBAAgB,OAAO,4CAA4C,iBAAiB;;ACgBpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,iBAAsB,QAAQ;oBACH,OAAO,gBAAgB,OAAO;aAC7C,eAAe,GACxB,OAAO;;;;;AAoBV;;;;;;;;;;AAUA,iBAAsB,WAAW;oBACN,OAAO,gBAAgB,OAAO;aAC7C,eAAe,GACxB,OAAO;oBAAoB,KAAK;;;;;ACrHnC;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,iBAAgB,WAAW;;;;;;;;;;;;;AC1B3B;;;;;;;;;;;;;;;;;;;;;;AAsBA,iBAAgB,MAAM,4BAA4B,KAAK;AAEvD;;;;;;;;;;;;;;AAcA,iBAAgB,MAAM,WAAW,KAAK;;ACpCtC,iBAAgB,SAAS,eAAe,MAAM,mBAAmB,OAAO;AAIxE,iBAAgB,SAAS,eAAe,MAAM,mBAAmB,OAAO;AAIxE,iBAAgB,OAAO,eAAe,OAAO;AAI7C,iBAAgB,OAAO,eAAe,OAAO;AAI7C;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,iBAAgB,WAAW","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -48,16 +48,46 @@ function goTry(value) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function
|
|
51
|
+
function taggedError(tag) {
|
|
52
|
+
return class TaggedErrorClass extends Error {
|
|
53
|
+
constructor(message, options) {
|
|
54
|
+
super(message);
|
|
55
|
+
this._tag = tag;
|
|
56
|
+
this.name = tag;
|
|
57
|
+
this.cause = options?.cause;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const UnknownError = taggedError("UnknownError");
|
|
63
|
+
|
|
64
|
+
function isTaggedError(err) {
|
|
65
|
+
return isError(err) && "_tag" in err && typeof err._tag === "string";
|
|
66
|
+
}
|
|
67
|
+
function goTryRaw(value, options) {
|
|
68
|
+
const { errorClass, systemErrorClass } = options || {};
|
|
69
|
+
const actualSystemErrorClass = systemErrorClass ?? UnknownError;
|
|
52
70
|
const wrapError = (err) => {
|
|
53
|
-
if (
|
|
71
|
+
if (errorClass) {
|
|
72
|
+
if (err === void 0) {
|
|
73
|
+
return new errorClass("undefined");
|
|
74
|
+
}
|
|
75
|
+
if (isError(err)) {
|
|
76
|
+
return new errorClass(err.message, { cause: err });
|
|
77
|
+
}
|
|
78
|
+
return new errorClass(String(err));
|
|
79
|
+
}
|
|
80
|
+
if (actualSystemErrorClass) {
|
|
81
|
+
if (isTaggedError(err)) {
|
|
82
|
+
return err;
|
|
83
|
+
}
|
|
54
84
|
if (err === void 0) {
|
|
55
|
-
return new
|
|
85
|
+
return new actualSystemErrorClass("undefined");
|
|
56
86
|
}
|
|
57
87
|
if (isError(err)) {
|
|
58
|
-
return new
|
|
88
|
+
return new actualSystemErrorClass(err.message, { cause: err });
|
|
59
89
|
}
|
|
60
|
-
return new
|
|
90
|
+
return new actualSystemErrorClass(String(err));
|
|
61
91
|
}
|
|
62
92
|
if (err === void 0) {
|
|
63
93
|
return new Error("undefined");
|
|
@@ -151,17 +181,6 @@ async function goTryAllRaw(items, options) {
|
|
|
151
181
|
return [errors, results];
|
|
152
182
|
}
|
|
153
183
|
|
|
154
|
-
function taggedError(tag) {
|
|
155
|
-
return class TaggedErrorClass extends Error {
|
|
156
|
-
constructor(message, options) {
|
|
157
|
-
super(message);
|
|
158
|
-
this._tag = tag;
|
|
159
|
-
this.name = tag;
|
|
160
|
-
this.cause = options?.cause;
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
184
|
function assert(condition, errorOrClass, message) {
|
|
166
185
|
if (!condition) {
|
|
167
186
|
if (typeof errorOrClass === "string") {
|
|
@@ -174,4 +193,4 @@ function assert(condition, errorOrClass, message) {
|
|
|
174
193
|
}
|
|
175
194
|
}
|
|
176
195
|
|
|
177
|
-
export { assert, assertNever, failure, goTry, goTryAll, goTryAllRaw, goTryOr, goTryRaw, isFailure, isSuccess, success, taggedError };
|
|
196
|
+
export { UnknownError, assert, assertNever, failure, goTry, goTryAll, goTryAllRaw, goTryOr, goTryRaw, isFailure, isSuccess, success, taggedError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "go-go-try",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"description": "Tries to execute a sync/async function, returns a result tuple",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "thelinuxlich/go-go-try",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
],
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@ark/attest": "^0.56.0",
|
|
51
|
-
"@biomejs/biome": "^2.
|
|
51
|
+
"@biomejs/biome": "^2.4.2",
|
|
52
52
|
"@vitest/coverage-v8": "^4.0.18",
|
|
53
53
|
"husky": "^9.1.7",
|
|
54
54
|
"lint-staged": "^16.2.7",
|
|
55
|
-
"pkgroll": "^2.
|
|
55
|
+
"pkgroll": "^2.26.2",
|
|
56
56
|
"tsx": "^4.21.0",
|
|
57
57
|
"typescript": "^5.9.3",
|
|
58
58
|
"vitest": "^4.0.18"
|
package/src/goTryRaw.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import type { Result,
|
|
1
|
+
import type { Result, GoTryRawOptions } from './types.js'
|
|
2
2
|
import { success, failure } from './result-helpers.js'
|
|
3
3
|
import { isPromise, isError } from './internals.js'
|
|
4
|
+
import { UnknownError } from './unknown-error.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a value is a tagged error (has a _tag property).
|
|
8
|
+
*/
|
|
9
|
+
function isTaggedError(err: unknown): err is { _tag: string } {
|
|
10
|
+
return isError(err) && '_tag' in err && typeof (err as { _tag?: unknown })._tag === 'string'
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
/**
|
|
6
14
|
* Executes a function, promise, or value and returns a Result type.
|
|
7
15
|
* If an error occurs, it returns a Failure with the raw error object.
|
|
8
16
|
*
|
|
9
17
|
* @template T The type of the successful result
|
|
10
|
-
* @template E The type of the error
|
|
18
|
+
* @template E The type of the error
|
|
11
19
|
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
|
|
12
|
-
* @param {
|
|
20
|
+
* @param {GoTryRawOptions<E>} [options] - Optional options object
|
|
13
21
|
* @returns {Result<E, T> | Promise<Result<E, T>>} A Result type or a Promise of a Result type
|
|
14
22
|
*
|
|
15
23
|
* @example
|
|
@@ -25,47 +33,76 @@ import { isPromise, isError } from './internals.js'
|
|
|
25
33
|
* const [err, result] = await goTryRaw(fetch('https://api.example.com/data'));
|
|
26
34
|
*
|
|
27
35
|
* @example
|
|
28
|
-
* // With
|
|
36
|
+
* // With options object - wrap all errors
|
|
29
37
|
* const DatabaseError = taggedError('DatabaseError');
|
|
30
|
-
* const [err, result] = await goTryRaw(fetchData(), DatabaseError);
|
|
31
|
-
*
|
|
38
|
+
* const [err, result] = await goTryRaw(fetchData(), { errorClass: DatabaseError });
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // With options object - systemErrorClass only wraps non-tagged errors
|
|
42
|
+
* const [err, result] = await goTryRaw(fetchData(), { systemErrorClass: UnknownError });
|
|
43
|
+
* // Errors thrown as tagged errors pass through
|
|
44
|
+
* // Other errors are wrapped in UnknownError
|
|
32
45
|
*/
|
|
33
|
-
export function goTryRaw<T
|
|
34
|
-
export function goTryRaw<T, E =
|
|
35
|
-
export function goTryRaw<T
|
|
46
|
+
export function goTryRaw<T>(fn: () => never): Result<Error, never>
|
|
47
|
+
export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => never, options: GoTryRawOptions<E>): Result<E, never>
|
|
48
|
+
export function goTryRaw<T>(
|
|
36
49
|
fn: () => Promise<T>,
|
|
37
|
-
): Promise<Result<
|
|
38
|
-
export function goTryRaw<T, E =
|
|
50
|
+
): Promise<Result<Error, T>>
|
|
51
|
+
export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(
|
|
39
52
|
fn: () => Promise<T>,
|
|
40
|
-
|
|
53
|
+
options: GoTryRawOptions<E>,
|
|
41
54
|
): Promise<Result<E, T>>
|
|
42
|
-
export function goTryRaw<T
|
|
55
|
+
export function goTryRaw<T>(
|
|
43
56
|
promise: Promise<T>,
|
|
44
|
-
): Promise<Result<
|
|
45
|
-
export function goTryRaw<T, E =
|
|
57
|
+
): Promise<Result<Error, T>>
|
|
58
|
+
export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(
|
|
46
59
|
promise: Promise<T>,
|
|
47
|
-
|
|
60
|
+
options: GoTryRawOptions<E>,
|
|
48
61
|
): Promise<Result<E, T>>
|
|
49
|
-
export function goTryRaw<T
|
|
50
|
-
export function goTryRaw<T, E =
|
|
51
|
-
export function goTryRaw<T
|
|
52
|
-
export function goTryRaw<T, E =
|
|
62
|
+
export function goTryRaw<T>(fn: () => T): Result<Error, T>
|
|
63
|
+
export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => T, options: GoTryRawOptions<E>): Result<E, T>
|
|
64
|
+
export function goTryRaw<T>(value: T): Result<Error, T>
|
|
65
|
+
export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(value: T, options: GoTryRawOptions<E>): Result<E, T>
|
|
53
66
|
export function goTryRaw<T, E = Error>(
|
|
54
67
|
value: T | Promise<T> | (() => T | Promise<T>),
|
|
55
|
-
|
|
68
|
+
options?: GoTryRawOptions<E>,
|
|
56
69
|
): Result<E, T> | Promise<Result<E, T>> {
|
|
57
|
-
|
|
70
|
+
const { errorClass, systemErrorClass } = options || {}
|
|
71
|
+
|
|
72
|
+
// Determine the actual system error class to use
|
|
73
|
+
// Default to UnknownError when systemErrorClass is not specified
|
|
74
|
+
const actualSystemErrorClass = systemErrorClass ?? UnknownError
|
|
75
|
+
|
|
76
|
+
// Helper to wrap error based on the options
|
|
58
77
|
const wrapError = (err: unknown): E => {
|
|
59
|
-
|
|
78
|
+
// If errorClass is specified, wrap all errors with it
|
|
79
|
+
if (errorClass) {
|
|
60
80
|
if (err === undefined) {
|
|
61
|
-
return new
|
|
81
|
+
return new errorClass('undefined')
|
|
62
82
|
}
|
|
63
83
|
if (isError(err)) {
|
|
64
|
-
return new
|
|
84
|
+
return new errorClass(err.message, { cause: err })
|
|
65
85
|
}
|
|
66
|
-
return new
|
|
86
|
+
return new errorClass(String(err))
|
|
67
87
|
}
|
|
68
|
-
|
|
88
|
+
|
|
89
|
+
// If actualSystemErrorClass is specified, only wrap non-tagged errors
|
|
90
|
+
if (actualSystemErrorClass) {
|
|
91
|
+
if (isTaggedError(err)) {
|
|
92
|
+
return err as unknown as E
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Wrap non-tagged errors with systemErrorClass
|
|
96
|
+
if (err === undefined) {
|
|
97
|
+
return new actualSystemErrorClass('undefined') as unknown as E
|
|
98
|
+
}
|
|
99
|
+
if (isError(err)) {
|
|
100
|
+
return new actualSystemErrorClass(err.message, { cause: err }) as unknown as E
|
|
101
|
+
}
|
|
102
|
+
return new actualSystemErrorClass(String(err)) as unknown as E
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// No options - original behavior: return raw error
|
|
69
106
|
if (err === undefined) {
|
|
70
107
|
return new Error('undefined') as unknown as E
|
|
71
108
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isSuccess,
|
|
16
16
|
success,
|
|
17
17
|
taggedError,
|
|
18
|
+
UnknownError,
|
|
18
19
|
} from './index.js'
|
|
19
20
|
|
|
20
21
|
test(`value returned by callback is used when callback doesn't throw`, async () => {
|
|
@@ -319,8 +320,12 @@ describe('edge cases', () => {
|
|
|
319
320
|
assert.equal(value1, undefined)
|
|
320
321
|
assert.equal(value2, undefined)
|
|
321
322
|
assert.equal(err1, 'custom error')
|
|
322
|
-
|
|
323
|
-
assert.equal(
|
|
323
|
+
// goTryRaw now wraps errors in UnknownError by default
|
|
324
|
+
assert.equal(err2 instanceof UnknownError, true)
|
|
325
|
+
assert.equal((err2 as InstanceType<typeof UnknownError>)?._tag, 'UnknownError')
|
|
326
|
+
assert.equal(err2?.message, 'custom error')
|
|
327
|
+
// Original error is preserved in cause
|
|
328
|
+
assert.equal(((err2 as unknown as { cause?: unknown })?.cause as CustomError)?.code, 500)
|
|
324
329
|
})
|
|
325
330
|
|
|
326
331
|
test('throwing a string', () => {
|
|
@@ -367,38 +372,41 @@ describe('assert helper', () => {
|
|
|
367
372
|
})
|
|
368
373
|
|
|
369
374
|
test('throws with string message when condition is false', () => {
|
|
375
|
+
let caught = false
|
|
370
376
|
try {
|
|
371
377
|
assertTry(false, 'custom error message')
|
|
372
|
-
// Should not reach here
|
|
373
|
-
assert.equal(true, false)
|
|
374
378
|
} catch (err) {
|
|
379
|
+
caught = true
|
|
375
380
|
assert.ok(err instanceof Error)
|
|
376
381
|
assert.equal((err as Error).message, 'custom error message')
|
|
377
382
|
}
|
|
383
|
+
assert.equal(caught, true)
|
|
378
384
|
})
|
|
379
385
|
|
|
380
386
|
test('throws with Error instance when condition is false', () => {
|
|
381
387
|
const customError = new Error('custom error instance')
|
|
388
|
+
let caught = false
|
|
382
389
|
try {
|
|
383
390
|
assertTry(false, customError)
|
|
384
|
-
// Should not reach here
|
|
385
|
-
assert.equal(true, false)
|
|
386
391
|
} catch (err) {
|
|
392
|
+
caught = true
|
|
387
393
|
assert.equal(err, customError)
|
|
388
394
|
}
|
|
395
|
+
assert.equal(caught, true)
|
|
389
396
|
})
|
|
390
397
|
|
|
391
398
|
test('throws with tagged error when condition is false', () => {
|
|
392
399
|
const DatabaseError = taggedError('DatabaseError')
|
|
400
|
+
let caught = false
|
|
393
401
|
try {
|
|
394
402
|
assertTry(false, new DatabaseError('database connection failed'))
|
|
395
|
-
// Should not reach here
|
|
396
|
-
assert.equal(true, false)
|
|
397
403
|
} catch (err) {
|
|
404
|
+
caught = true
|
|
398
405
|
assert.ok(err instanceof DatabaseError)
|
|
399
406
|
assert.equal((err as InstanceType<typeof DatabaseError>)._tag, 'DatabaseError')
|
|
400
407
|
assert.equal((err as Error).message, 'database connection failed')
|
|
401
408
|
}
|
|
409
|
+
assert.equal(caught, true)
|
|
402
410
|
})
|
|
403
411
|
|
|
404
412
|
test('type narrowing works with Result types using err === undefined', () => {
|
|
@@ -434,7 +442,7 @@ describe('assert helper', () => {
|
|
|
434
442
|
|
|
435
443
|
test('type narrowing works with tagged errors', () => {
|
|
436
444
|
const DatabaseError = taggedError('DatabaseError')
|
|
437
|
-
const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), DatabaseError)
|
|
445
|
+
const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), { errorClass: DatabaseError })
|
|
438
446
|
|
|
439
447
|
// Before assert
|
|
440
448
|
attest<InstanceType<typeof DatabaseError> | undefined>(err)
|
|
@@ -452,13 +460,13 @@ describe('assert helper', () => {
|
|
|
452
460
|
const DatabaseError = taggedError('DatabaseError')
|
|
453
461
|
|
|
454
462
|
function fetchUserOldStyle(): Result<InstanceType<typeof DatabaseError>, { id: string }> {
|
|
455
|
-
const [err, user] = goTryRaw(() => ({ id: '123' }), DatabaseError)
|
|
463
|
+
const [err, user] = goTryRaw(() => ({ id: '123' }), { errorClass: DatabaseError })
|
|
456
464
|
if (err) return failure(err) // Old style
|
|
457
465
|
return [undefined, user] as const
|
|
458
466
|
}
|
|
459
467
|
|
|
460
468
|
function fetchUserNewStyle(): Result<InstanceType<typeof DatabaseError>, { id: string }> {
|
|
461
|
-
const [err, user] = goTryRaw(() => ({ id: '123' }), DatabaseError)
|
|
469
|
+
const [err, user] = goTryRaw(() => ({ id: '123' }), { errorClass: DatabaseError })
|
|
462
470
|
assertTry(err === undefined, new DatabaseError('Failed to fetch user'))
|
|
463
471
|
// TypeScript now knows user is defined
|
|
464
472
|
return [undefined, user] as const
|
|
@@ -526,7 +534,7 @@ describe('assert helper', () => {
|
|
|
526
534
|
|
|
527
535
|
test('shorter syntax with tagged errors provides type narrowing', () => {
|
|
528
536
|
const DatabaseError = taggedError('DatabaseError')
|
|
529
|
-
const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), DatabaseError)
|
|
537
|
+
const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), { errorClass: DatabaseError })
|
|
530
538
|
|
|
531
539
|
// Before assert
|
|
532
540
|
attest<InstanceType<typeof DatabaseError> | undefined>(err)
|
|
@@ -1187,8 +1195,8 @@ describe('taggedError', () => {
|
|
|
1187
1195
|
}
|
|
1188
1196
|
|
|
1189
1197
|
// Wrap in functions so goTryRaw can catch the errors
|
|
1190
|
-
const [dbErr, dbResult] = goTryRaw(fetchFromDb, DatabaseError)
|
|
1191
|
-
const [netErr, netResult] = goTryRaw(fetchFromNetwork, NetworkError)
|
|
1198
|
+
const [dbErr, dbResult] = goTryRaw(fetchFromDb, { errorClass: DatabaseError })
|
|
1199
|
+
const [netErr, netResult] = goTryRaw(fetchFromNetwork, { errorClass: NetworkError })
|
|
1192
1200
|
|
|
1193
1201
|
// Type narrowing via discriminated union
|
|
1194
1202
|
if (dbErr) {
|
|
@@ -1214,14 +1222,14 @@ describe('taggedError', () => {
|
|
|
1214
1222
|
async function fetchUser(id: string): Promise<Result<AppError, { id: string; name: string }>> {
|
|
1215
1223
|
const [dbErr, user] = await goTryRaw(
|
|
1216
1224
|
Promise.resolve({ id, name: 'John' }),
|
|
1217
|
-
DatabaseError,
|
|
1225
|
+
{ errorClass: DatabaseError },
|
|
1218
1226
|
)
|
|
1219
1227
|
if (dbErr) return failure<AppError>(dbErr)
|
|
1220
1228
|
return [undefined, user] as const
|
|
1221
1229
|
}
|
|
1222
1230
|
|
|
1223
1231
|
async function fetchData(): Promise<Result<AppError, string>> {
|
|
1224
|
-
const [netErr, data] = await goTryRaw(Promise.resolve('data'), NetworkError)
|
|
1232
|
+
const [netErr, data] = await goTryRaw(Promise.resolve('data'), { errorClass: NetworkError })
|
|
1225
1233
|
if (netErr) return failure<AppError>(netErr)
|
|
1226
1234
|
return [undefined, data] as const
|
|
1227
1235
|
}
|
|
@@ -1374,7 +1382,7 @@ describe('TaggedUnion type helper', () => {
|
|
|
1374
1382
|
async function fetchData(): Promise<Result<AppError, string>> {
|
|
1375
1383
|
const [err, data] = await goTryRaw(
|
|
1376
1384
|
Promise.reject(new Error('timeout')),
|
|
1377
|
-
NetworkError,
|
|
1385
|
+
{ errorClass: NetworkError },
|
|
1378
1386
|
)
|
|
1379
1387
|
if (err) return failure<AppError>(err)
|
|
1380
1388
|
return [undefined, data] as const
|
|
@@ -1398,14 +1406,14 @@ describe('inferred return types with tagged errors', () => {
|
|
|
1398
1406
|
// First operation might fail with DatabaseError
|
|
1399
1407
|
const [dbErr, user] = await goTryRaw(
|
|
1400
1408
|
Promise.resolve({ id, name: 'John' }),
|
|
1401
|
-
DatabaseError,
|
|
1409
|
+
{ errorClass: DatabaseError },
|
|
1402
1410
|
)
|
|
1403
1411
|
if (dbErr) return failure(dbErr)
|
|
1404
1412
|
|
|
1405
1413
|
// Second operation might fail with NetworkError
|
|
1406
1414
|
const [netErr, enriched] = await goTryRaw(
|
|
1407
1415
|
Promise.resolve({ ...user!, email: 'john@example.com' }),
|
|
1408
|
-
NetworkError,
|
|
1416
|
+
{ errorClass: NetworkError },
|
|
1409
1417
|
)
|
|
1410
1418
|
if (netErr) return failure(netErr)
|
|
1411
1419
|
|
|
@@ -1443,14 +1451,14 @@ describe('inferred return types with tagged errors', () => {
|
|
|
1443
1451
|
// No explicit return type annotation
|
|
1444
1452
|
function processConfig(input: string) {
|
|
1445
1453
|
// Parse step
|
|
1446
|
-
const [parseErr, parsed] = goTryRaw(() => JSON.parse(input), ParseError)
|
|
1454
|
+
const [parseErr, parsed] = goTryRaw(() => JSON.parse(input), { errorClass: ParseError })
|
|
1447
1455
|
if (parseErr) return failure(parseErr)
|
|
1448
1456
|
|
|
1449
1457
|
// Validate step
|
|
1450
1458
|
const [validateErr, validated] = goTryRaw(() => {
|
|
1451
1459
|
if (!parsed!.port) throw new Error('Missing port')
|
|
1452
1460
|
return parsed as { port: number }
|
|
1453
|
-
}, ValidateError)
|
|
1461
|
+
}, { errorClass: ValidateError })
|
|
1454
1462
|
if (validateErr) return failure(validateErr)
|
|
1455
1463
|
|
|
1456
1464
|
return [undefined, validated] as const
|
|
@@ -1481,19 +1489,19 @@ describe('inferred return types with tagged errors', () => {
|
|
|
1481
1489
|
async function complexOperation(shouldFail: 'a' | 'b' | 'c' | 'none') {
|
|
1482
1490
|
const [errA, valA] = await goTryRaw(
|
|
1483
1491
|
shouldFail === 'a' ? Promise.reject(new Error('a')) : Promise.resolve('step1'),
|
|
1484
|
-
ErrorA,
|
|
1492
|
+
{ errorClass: ErrorA },
|
|
1485
1493
|
)
|
|
1486
1494
|
if (errA) return failure(errA)
|
|
1487
1495
|
|
|
1488
1496
|
const [errB, valB] = await goTryRaw(
|
|
1489
1497
|
shouldFail === 'b' ? Promise.reject(new Error('b')) : Promise.resolve('step2'),
|
|
1490
|
-
ErrorB,
|
|
1498
|
+
{ errorClass: ErrorB },
|
|
1491
1499
|
)
|
|
1492
1500
|
if (errB) return failure(errB)
|
|
1493
1501
|
|
|
1494
1502
|
const [errC, valC] = await goTryRaw(
|
|
1495
1503
|
shouldFail === 'c' ? Promise.reject(new Error('c')) : Promise.resolve('step3'),
|
|
1496
|
-
ErrorC,
|
|
1504
|
+
{ errorClass: ErrorC },
|
|
1497
1505
|
)
|
|
1498
1506
|
if (errC) return failure(errC)
|
|
1499
1507
|
|
|
@@ -1530,3 +1538,204 @@ describe('inferred return types with tagged errors', () => {
|
|
|
1530
1538
|
)
|
|
1531
1539
|
})
|
|
1532
1540
|
})
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
describe('UnknownError', () => {
|
|
1544
|
+
test('UnknownError is exported as a tagged error class', () => {
|
|
1545
|
+
const err = new UnknownError('something went wrong')
|
|
1546
|
+
assert.equal(err._tag, 'UnknownError')
|
|
1547
|
+
assert.equal(err.message, 'something went wrong')
|
|
1548
|
+
assert.equal(err.name, 'UnknownError')
|
|
1549
|
+
assert.ok(err instanceof Error)
|
|
1550
|
+
assert.ok(err instanceof UnknownError)
|
|
1551
|
+
})
|
|
1552
|
+
|
|
1553
|
+
test('UnknownError supports cause option', () => {
|
|
1554
|
+
const cause = new Error('original error')
|
|
1555
|
+
const err = new UnknownError('wrapped error', { cause })
|
|
1556
|
+
assert.equal(err.cause, cause)
|
|
1557
|
+
})
|
|
1558
|
+
|
|
1559
|
+
test('goTryRaw defaults to UnknownError for system errors', () => {
|
|
1560
|
+
const fn = () => {
|
|
1561
|
+
throw new Error('system error')
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
const [err, value] = goTryRaw(fn)
|
|
1565
|
+
|
|
1566
|
+
assert.equal(value, undefined)
|
|
1567
|
+
assert.ok(err instanceof UnknownError)
|
|
1568
|
+
assert.equal(err._tag, 'UnknownError')
|
|
1569
|
+
assert.equal(err.message, 'system error')
|
|
1570
|
+
})
|
|
1571
|
+
|
|
1572
|
+
test('goTryRaw defaults to UnknownError for thrown strings', () => {
|
|
1573
|
+
const fn = () => {
|
|
1574
|
+
throw 'string error'
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const [err, value] = goTryRaw(fn)
|
|
1578
|
+
|
|
1579
|
+
assert.equal(value, undefined)
|
|
1580
|
+
assert.ok(err instanceof UnknownError)
|
|
1581
|
+
assert.equal(err._tag, 'UnknownError')
|
|
1582
|
+
assert.equal(err.message, 'string error')
|
|
1583
|
+
})
|
|
1584
|
+
|
|
1585
|
+
test('goTryRaw defaults to UnknownError for thrown undefined', () => {
|
|
1586
|
+
const fn = () => {
|
|
1587
|
+
throw undefined
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
const [err, value] = goTryRaw(fn)
|
|
1591
|
+
|
|
1592
|
+
assert.equal(value, undefined)
|
|
1593
|
+
assert.ok(err instanceof UnknownError)
|
|
1594
|
+
assert.equal(err._tag, 'UnknownError')
|
|
1595
|
+
assert.equal(err.message, 'undefined')
|
|
1596
|
+
})
|
|
1597
|
+
|
|
1598
|
+
test('goTryRaw with async defaults to UnknownError', async () => {
|
|
1599
|
+
const promise = Promise.reject(new Error('async error'))
|
|
1600
|
+
|
|
1601
|
+
const [err, value] = await goTryRaw(promise)
|
|
1602
|
+
|
|
1603
|
+
assert.equal(value, undefined)
|
|
1604
|
+
assert.ok(err instanceof UnknownError)
|
|
1605
|
+
assert.equal(err._tag, 'UnknownError')
|
|
1606
|
+
assert.equal(err.message, 'async error')
|
|
1607
|
+
})
|
|
1608
|
+
})
|
|
1609
|
+
|
|
1610
|
+
describe('goTryRaw with options object', () => {
|
|
1611
|
+
test('errorClass wraps all errors including tagged ones', () => {
|
|
1612
|
+
const DatabaseError = taggedError('DatabaseError')
|
|
1613
|
+
const NetworkError = taggedError('NetworkError')
|
|
1614
|
+
|
|
1615
|
+
// When a DatabaseError is thrown, it gets wrapped in NetworkError
|
|
1616
|
+
const fn = () => {
|
|
1617
|
+
throw new DatabaseError('db connection failed')
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
const [err, value] = goTryRaw(fn, { errorClass: NetworkError })
|
|
1621
|
+
|
|
1622
|
+
assert.equal(value, undefined)
|
|
1623
|
+
assert.ok(err instanceof NetworkError)
|
|
1624
|
+
assert.equal(err._tag, 'NetworkError')
|
|
1625
|
+
assert.equal(err.message, 'db connection failed')
|
|
1626
|
+
// Original error is preserved in cause
|
|
1627
|
+
assert.ok(err.cause instanceof DatabaseError)
|
|
1628
|
+
})
|
|
1629
|
+
|
|
1630
|
+
test('errorClass wraps non-tagged errors', () => {
|
|
1631
|
+
const NetworkError = taggedError('NetworkError')
|
|
1632
|
+
|
|
1633
|
+
const fn = () => {
|
|
1634
|
+
throw new Error('plain error')
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
const [err, value] = goTryRaw(fn, { errorClass: NetworkError })
|
|
1638
|
+
|
|
1639
|
+
assert.equal(value, undefined)
|
|
1640
|
+
assert.ok(err instanceof NetworkError)
|
|
1641
|
+
assert.equal(err._tag, 'NetworkError')
|
|
1642
|
+
assert.equal(err.message, 'plain error')
|
|
1643
|
+
})
|
|
1644
|
+
|
|
1645
|
+
test('systemErrorClass only wraps non-tagged errors', () => {
|
|
1646
|
+
const DatabaseError = taggedError('DatabaseError')
|
|
1647
|
+
const SystemError = taggedError('SystemError')
|
|
1648
|
+
|
|
1649
|
+
// Tagged errors should pass through
|
|
1650
|
+
const fnTagged = () => {
|
|
1651
|
+
throw new DatabaseError('db error')
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
const [err1, value1] = goTryRaw(fnTagged, { systemErrorClass: SystemError })
|
|
1655
|
+
|
|
1656
|
+
assert.equal(value1, undefined)
|
|
1657
|
+
assert.ok(err1 instanceof DatabaseError)
|
|
1658
|
+
assert.equal(err1._tag, 'DatabaseError')
|
|
1659
|
+
|
|
1660
|
+
// Non-tagged errors should be wrapped
|
|
1661
|
+
const fnPlain = () => {
|
|
1662
|
+
throw new Error('system error')
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
const [err2, value2] = goTryRaw(fnPlain, { systemErrorClass: SystemError })
|
|
1666
|
+
|
|
1667
|
+
assert.equal(value2, undefined)
|
|
1668
|
+
assert.ok(err2 instanceof SystemError)
|
|
1669
|
+
assert.equal(err2._tag, 'SystemError')
|
|
1670
|
+
assert.equal(err2.message, 'system error')
|
|
1671
|
+
})
|
|
1672
|
+
|
|
1673
|
+
test('systemErrorClass defaults to UnknownError when not specified', () => {
|
|
1674
|
+
const fn = () => {
|
|
1675
|
+
throw new Error('plain error')
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
const [err, value] = goTryRaw(fn, {})
|
|
1679
|
+
|
|
1680
|
+
assert.equal(value, undefined)
|
|
1681
|
+
assert.ok(err instanceof UnknownError)
|
|
1682
|
+
assert.equal(err._tag, 'UnknownError')
|
|
1683
|
+
})
|
|
1684
|
+
|
|
1685
|
+
test('async with options object', async () => {
|
|
1686
|
+
const DatabaseError = taggedError('DatabaseError')
|
|
1687
|
+
|
|
1688
|
+
const promise = Promise.reject(new Error('async error'))
|
|
1689
|
+
|
|
1690
|
+
const [err, value] = await goTryRaw(promise, { errorClass: DatabaseError })
|
|
1691
|
+
|
|
1692
|
+
assert.equal(value, undefined)
|
|
1693
|
+
assert.ok(err instanceof DatabaseError)
|
|
1694
|
+
assert.equal(err._tag, 'DatabaseError')
|
|
1695
|
+
})
|
|
1696
|
+
|
|
1697
|
+
})
|
|
1698
|
+
|
|
1699
|
+
describe('goTryRaw options type tests', () => {
|
|
1700
|
+
test('systemErrorClass preserves tagged errors', () => {
|
|
1701
|
+
const DatabaseError = taggedError('DatabaseError')
|
|
1702
|
+
const SystemError = taggedError('SystemError')
|
|
1703
|
+
|
|
1704
|
+
// Wrap in a function so goTryRaw can catch the error
|
|
1705
|
+
const [err, _value] = goTryRaw(() => {
|
|
1706
|
+
throw new DatabaseError('db error')
|
|
1707
|
+
}, { systemErrorClass: SystemError })
|
|
1708
|
+
|
|
1709
|
+
// Type is systemErrorClass since TypeScript cannot know which tagged errors
|
|
1710
|
+
// might be thrown at runtime (tagged errors pass through, others get wrapped)
|
|
1711
|
+
attest<InstanceType<typeof SystemError> | undefined>(err)
|
|
1712
|
+
|
|
1713
|
+
// But at runtime, tagged errors are preserved
|
|
1714
|
+
if (err) {
|
|
1715
|
+
assert.equal(err._tag, 'DatabaseError')
|
|
1716
|
+
}
|
|
1717
|
+
})
|
|
1718
|
+
|
|
1719
|
+
test('errorClass wraps all errors to specified type', () => {
|
|
1720
|
+
const DatabaseError = taggedError('DatabaseError')
|
|
1721
|
+
|
|
1722
|
+
const [err, value] = goTryRaw(() => 'test', { errorClass: DatabaseError })
|
|
1723
|
+
|
|
1724
|
+
attest<InstanceType<typeof DatabaseError> | undefined>(err)
|
|
1725
|
+
attest<string | undefined>(value)
|
|
1726
|
+
})
|
|
1727
|
+
|
|
1728
|
+
test('no options defaults to Error type (backward compatible)', () => {
|
|
1729
|
+
const [err, value] = goTryRaw(() => 'test')
|
|
1730
|
+
|
|
1731
|
+
attest<Error | undefined>(err)
|
|
1732
|
+
attest<string | undefined>(value)
|
|
1733
|
+
})
|
|
1734
|
+
|
|
1735
|
+
test('empty options object defaults to UnknownError type', () => {
|
|
1736
|
+
const [err, value] = goTryRaw(() => 'test', {})
|
|
1737
|
+
|
|
1738
|
+
attest<InstanceType<typeof UnknownError> | undefined>(err)
|
|
1739
|
+
attest<string | undefined>(value)
|
|
1740
|
+
})
|
|
1741
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type {
|
|
|
9
9
|
GoTryAllOptions,
|
|
10
10
|
ErrorConstructor,
|
|
11
11
|
TaggedUnion,
|
|
12
|
+
GoTryRawOptions,
|
|
12
13
|
} from './types.js'
|
|
13
14
|
|
|
14
15
|
// Export core functions
|
|
@@ -21,3 +22,6 @@ export { goTryAll, goTryAllRaw } from './goTryAll.js'
|
|
|
21
22
|
export { taggedError } from './tagged-error.js'
|
|
22
23
|
export { assert } from './assert.js'
|
|
23
24
|
export { isSuccess, isFailure, success, failure, assertNever } from './result-helpers.js'
|
|
25
|
+
|
|
26
|
+
// Export UnknownError tagged error
|
|
27
|
+
export { UnknownError } from './unknown-error.js'
|
package/src/types.ts
CHANGED
|
@@ -33,6 +33,20 @@ export interface GoTryAllOptions {
|
|
|
33
33
|
*/
|
|
34
34
|
export type ErrorConstructor<E> = new (message: string, options?: { cause?: unknown }) => E
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Checks if a value is a tagged error (has a _tag property).
|
|
38
|
+
*/
|
|
39
|
+
export type IsTaggedError<T> = T extends { _tag: string } ? true : false
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Options for goTryRaw function.
|
|
43
|
+
* errorClass and systemErrorClass are mutually exclusive - you can only provide one.
|
|
44
|
+
*/
|
|
45
|
+
export type GoTryRawOptions<E = Error> =
|
|
46
|
+
| { errorClass: ErrorConstructor<E>; systemErrorClass?: never }
|
|
47
|
+
| { errorClass?: never; systemErrorClass: ErrorConstructor<E> }
|
|
48
|
+
| { errorClass?: never; systemErrorClass?: never }
|
|
49
|
+
|
|
36
50
|
/**
|
|
37
51
|
* Creates a union type from multiple tagged error classes.
|
|
38
52
|
*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { taggedError } from './tagged-error.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default system error class for errors that aren't already wrapped in a tagged error class.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* // By default, goTryRaw wraps unknown errors in UnknownError
|
|
8
|
+
* const [err, result] = goTryRaw(() => mightThrow())
|
|
9
|
+
* if (err) {
|
|
10
|
+
* console.log(err._tag) // 'UnknownError'
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Use a custom system error class
|
|
15
|
+
* const SystemError = taggedError('SystemError')
|
|
16
|
+
* const [err, result] = goTryRaw(() => mightThrow(), {
|
|
17
|
+
* systemErrorClass: SystemError
|
|
18
|
+
* })
|
|
19
|
+
*/
|
|
20
|
+
export const UnknownError = taggedError('UnknownError')
|