errore 0.8.1 → 0.8.2
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/dist/core.d.ts +69 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +62 -0
- package/dist/error.d.ts +125 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +160 -0
- package/dist/extract.d.ts +52 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +77 -0
- package/dist/factory.d.ts +148 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +164 -0
- package/dist/index.d.ts +9 -471
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -289
- package/dist/transform.d.ts +58 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +87 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +4 -4
- package/src/core.ts +107 -0
- package/src/error.ts +237 -0
- package/src/extract.ts +86 -0
- package/src/factory.ts +270 -0
- package/src/index.test.ts +1057 -0
- package/src/index.ts +19 -0
- package/src/transform.ts +101 -0
- package/src/types.ts +25 -0
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { UnhandledError } from './error.js';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard: checks if value is an Error.
|
|
4
|
+
* After this check, TypeScript narrows the type to the error types in the union.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const result = await fetchUser(id)
|
|
8
|
+
* if (isError(result)) {
|
|
9
|
+
* // result is narrowed to the error type
|
|
10
|
+
* return result
|
|
11
|
+
* }
|
|
12
|
+
* // result is narrowed to User
|
|
13
|
+
* console.log(result.name)
|
|
14
|
+
*/
|
|
15
|
+
export declare function isError<V>(value: V): value is Extract<V, Error>;
|
|
16
|
+
/**
|
|
17
|
+
* Type guard: checks if value is NOT an Error.
|
|
18
|
+
* Inverse of isError for convenience.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const result = await fetchUser(id)
|
|
22
|
+
* if (isOk(result)) {
|
|
23
|
+
* console.log(result.name) // result is User
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
export declare function isOk<V>(value: V): value is Exclude<V, Error>;
|
|
27
|
+
/**
|
|
28
|
+
* Execute a sync function and return either the value or an error.
|
|
29
|
+
*
|
|
30
|
+
* @overload Simple form - wraps exceptions in UnhandledError
|
|
31
|
+
* @example
|
|
32
|
+
* const result = tryFn(() => JSON.parse(input))
|
|
33
|
+
* // result: UnhandledError | unknown
|
|
34
|
+
*
|
|
35
|
+
* @overload With custom catch - you control the error type
|
|
36
|
+
* @example
|
|
37
|
+
* const result = tryFn({
|
|
38
|
+
* try: () => JSON.parse(input),
|
|
39
|
+
* catch: (e) => new ParseError({ cause: e })
|
|
40
|
+
* })
|
|
41
|
+
* // result: ParseError | unknown
|
|
42
|
+
*/
|
|
43
|
+
export declare function tryFn<T>(fn: () => T): UnhandledError | T;
|
|
44
|
+
export declare function tryFn<T, E extends Error>(opts: {
|
|
45
|
+
try: () => T;
|
|
46
|
+
catch: (e: unknown) => E;
|
|
47
|
+
}): E | T;
|
|
48
|
+
/**
|
|
49
|
+
* Execute an async function and return either the value or an error.
|
|
50
|
+
*
|
|
51
|
+
* @overload Simple form - wraps exceptions in UnhandledError
|
|
52
|
+
* @example
|
|
53
|
+
* const result = await tryAsync(() => fetch(url).then(r => r.json()))
|
|
54
|
+
* // result: UnhandledError | unknown
|
|
55
|
+
*
|
|
56
|
+
* @overload With custom catch - you control the error type
|
|
57
|
+
* @example
|
|
58
|
+
* const result = await tryAsync({
|
|
59
|
+
* try: () => fetch(url),
|
|
60
|
+
* catch: (e) => new NetworkError({ cause: e })
|
|
61
|
+
* })
|
|
62
|
+
* // result: NetworkError | Response
|
|
63
|
+
*/
|
|
64
|
+
export declare function tryAsync<T>(fn: () => Promise<T>): Promise<UnhandledError | T>;
|
|
65
|
+
export declare function tryAsync<T, E extends Error>(opts: {
|
|
66
|
+
try: () => Promise<T>;
|
|
67
|
+
catch: (e: unknown) => E | Promise<E>;
|
|
68
|
+
}): Promise<E | T>;
|
|
69
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAE/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAE5D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,cAAc,GAAG,CAAC,CAAA;AACzD,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,KAAK,EAAE,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,CAAC,CAAA;AAmBlG;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,GAAG,CAAC,CAAC,CAAA;AAC9E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,KAAK,EAAE,IAAI,EAAE;IACjD,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAA;IACrB,KAAK,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CACtC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA"}
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { UnhandledError } from './error.js';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard: checks if value is an Error.
|
|
4
|
+
* After this check, TypeScript narrows the type to the error types in the union.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const result = await fetchUser(id)
|
|
8
|
+
* if (isError(result)) {
|
|
9
|
+
* // result is narrowed to the error type
|
|
10
|
+
* return result
|
|
11
|
+
* }
|
|
12
|
+
* // result is narrowed to User
|
|
13
|
+
* console.log(result.name)
|
|
14
|
+
*/
|
|
15
|
+
export function isError(value) {
|
|
16
|
+
return value instanceof Error;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Type guard: checks if value is NOT an Error.
|
|
20
|
+
* Inverse of isError for convenience.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const result = await fetchUser(id)
|
|
24
|
+
* if (isOk(result)) {
|
|
25
|
+
* console.log(result.name) // result is User
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
export function isOk(value) {
|
|
29
|
+
return !(value instanceof Error);
|
|
30
|
+
}
|
|
31
|
+
export function tryFn(fnOrOpts) {
|
|
32
|
+
if (typeof fnOrOpts === 'function') {
|
|
33
|
+
try {
|
|
34
|
+
return fnOrOpts();
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
return new UnhandledError({ cause });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
return fnOrOpts.try();
|
|
42
|
+
}
|
|
43
|
+
catch (cause) {
|
|
44
|
+
return fnOrOpts.catch(cause);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function tryAsync(fnOrOpts) {
|
|
48
|
+
if (typeof fnOrOpts === 'function') {
|
|
49
|
+
try {
|
|
50
|
+
return await fnOrOpts();
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
return new UnhandledError({ cause });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return await fnOrOpts.try();
|
|
58
|
+
}
|
|
59
|
+
catch (cause) {
|
|
60
|
+
return await fnOrOpts.catch(cause);
|
|
61
|
+
}
|
|
62
|
+
}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Any tagged error (for generic constraints)
|
|
3
|
+
*/
|
|
4
|
+
type AnyTaggedError = Error & {
|
|
5
|
+
readonly _tag: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Any class that extends Error
|
|
9
|
+
*/
|
|
10
|
+
type ErrorClass = new (...args: any[]) => Error;
|
|
11
|
+
/**
|
|
12
|
+
* Instance type produced by TaggedError factory
|
|
13
|
+
*/
|
|
14
|
+
export type TaggedErrorInstance<Tag extends string, Props, Base extends Error = Error> = Base & {
|
|
15
|
+
readonly _tag: Tag;
|
|
16
|
+
toJSON(): object;
|
|
17
|
+
} & Readonly<Props>;
|
|
18
|
+
/**
|
|
19
|
+
* Class type produced by TaggedError factory
|
|
20
|
+
*/
|
|
21
|
+
export type TaggedErrorClass<Tag extends string, Props, Base extends Error = Error> = {
|
|
22
|
+
new (...args: keyof Props extends never ? [args?: {}] : [args: Props]): TaggedErrorInstance<Tag, Props, Base>;
|
|
23
|
+
/** Type guard for this error class */
|
|
24
|
+
is(value: unknown): value is TaggedErrorInstance<Tag, Props, Base>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Factory for tagged error classes with discriminated _tag property.
|
|
28
|
+
* Enables exhaustive pattern matching on error unions.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* class NotFoundError extends TaggedError("NotFoundError")<{
|
|
32
|
+
* id: string;
|
|
33
|
+
* message: string;
|
|
34
|
+
* }>() {}
|
|
35
|
+
*
|
|
36
|
+
* const err = new NotFoundError({ id: "123", message: "Not found" });
|
|
37
|
+
* err._tag // "NotFoundError"
|
|
38
|
+
* err.id // "123"
|
|
39
|
+
*
|
|
40
|
+
* // Type guard
|
|
41
|
+
* NotFoundError.is(err) // true
|
|
42
|
+
* TaggedError.is(err) // true (any tagged error)
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // With custom base class
|
|
46
|
+
* class AppError extends Error {
|
|
47
|
+
* statusCode: number = 500
|
|
48
|
+
* report() { console.log(this.message) }
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* class NotFoundError extends TaggedError("NotFoundError", AppError)<{
|
|
52
|
+
* id: string;
|
|
53
|
+
* message: string;
|
|
54
|
+
* }>() {
|
|
55
|
+
* statusCode = 404
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* const err = new NotFoundError({ id: "123", message: "Not found" });
|
|
59
|
+
* err.statusCode // 404
|
|
60
|
+
* err.report() // works
|
|
61
|
+
*/
|
|
62
|
+
export declare const TaggedError: {
|
|
63
|
+
<Tag extends string, BaseClass extends ErrorClass = typeof Error>(tag: Tag, BaseClass?: BaseClass): <Props extends Record<string, unknown> = {}>() => TaggedErrorClass<Tag, Props, InstanceType<BaseClass>>;
|
|
64
|
+
/** Type guard for any TaggedError instance */
|
|
65
|
+
is(value: unknown): value is AnyTaggedError;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Type guard for tagged error instances.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* if (isTaggedError(value)) { value._tag }
|
|
72
|
+
*/
|
|
73
|
+
export declare const isTaggedError: (value: unknown) => value is AnyTaggedError;
|
|
74
|
+
/**
|
|
75
|
+
* Handler map that includes `_` for plain Error (untagged)
|
|
76
|
+
*/
|
|
77
|
+
type MatchHandlersWithPlain<E extends Error, R> = {
|
|
78
|
+
[K in Extract<E, AnyTaggedError>['_tag']]: (err: Extract<E, {
|
|
79
|
+
_tag: K;
|
|
80
|
+
}>) => R;
|
|
81
|
+
} & (Exclude<E, AnyTaggedError> extends never ? {} : {
|
|
82
|
+
_: (err: Exclude<E, AnyTaggedError>) => R;
|
|
83
|
+
});
|
|
84
|
+
/**
|
|
85
|
+
* Exhaustive pattern match on error union by _tag.
|
|
86
|
+
* Use `_` handler for plain Error instances without _tag.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // Tagged errors only
|
|
90
|
+
* matchError(err, {
|
|
91
|
+
* NotFoundError: (e) => `Missing: ${e.id}`,
|
|
92
|
+
* ValidationError: (e) => `Invalid: ${e.field}`,
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* // Mixed tagged and plain Error
|
|
97
|
+
* matchError(err, {
|
|
98
|
+
* NotFoundError: (e) => `Missing: ${e.id}`,
|
|
99
|
+
* _: (e) => `Unknown error: ${e.message}`,
|
|
100
|
+
* });
|
|
101
|
+
*/
|
|
102
|
+
export declare function matchError<E extends Error, R>(err: E, handlers: MatchHandlersWithPlain<E, R>): R;
|
|
103
|
+
/**
|
|
104
|
+
* Partial pattern match with fallback for unhandled tags.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* matchErrorPartial(err, {
|
|
108
|
+
* NotFoundError: (e) => `Missing: ${e.id}`,
|
|
109
|
+
* }, (e) => `Unknown: ${e.message}`);
|
|
110
|
+
*/
|
|
111
|
+
export declare function matchErrorPartial<E extends Error, R>(err: E, handlers: Partial<MatchHandlersWithPlain<E, R>>, fallback: (e: E) => R): R;
|
|
112
|
+
declare const UnhandledError_base: TaggedErrorClass<"UnhandledError", {
|
|
113
|
+
message: string;
|
|
114
|
+
cause: unknown;
|
|
115
|
+
}, Error>;
|
|
116
|
+
/**
|
|
117
|
+
* Default error type when catching unknown exceptions.
|
|
118
|
+
*/
|
|
119
|
+
export declare class UnhandledError extends UnhandledError_base {
|
|
120
|
+
constructor(args: {
|
|
121
|
+
cause: unknown;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
export {};
|
|
125
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"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"}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize cause for JSON output
|
|
3
|
+
*/
|
|
4
|
+
const serializeCause = (cause) => {
|
|
5
|
+
if (cause instanceof Error) {
|
|
6
|
+
return { name: cause.name, message: cause.message, stack: cause.stack };
|
|
7
|
+
}
|
|
8
|
+
return cause;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Type guard for any tagged error
|
|
12
|
+
*/
|
|
13
|
+
const isAnyTaggedError = (value) => {
|
|
14
|
+
return value instanceof Error && '_tag' in value && typeof value._tag === 'string';
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Factory for tagged error classes with discriminated _tag property.
|
|
18
|
+
* Enables exhaustive pattern matching on error unions.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* class NotFoundError extends TaggedError("NotFoundError")<{
|
|
22
|
+
* id: string;
|
|
23
|
+
* message: string;
|
|
24
|
+
* }>() {}
|
|
25
|
+
*
|
|
26
|
+
* const err = new NotFoundError({ id: "123", message: "Not found" });
|
|
27
|
+
* err._tag // "NotFoundError"
|
|
28
|
+
* err.id // "123"
|
|
29
|
+
*
|
|
30
|
+
* // Type guard
|
|
31
|
+
* NotFoundError.is(err) // true
|
|
32
|
+
* TaggedError.is(err) // true (any tagged error)
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // With custom base class
|
|
36
|
+
* class AppError extends Error {
|
|
37
|
+
* statusCode: number = 500
|
|
38
|
+
* report() { console.log(this.message) }
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* class NotFoundError extends TaggedError("NotFoundError", AppError)<{
|
|
42
|
+
* id: string;
|
|
43
|
+
* message: string;
|
|
44
|
+
* }>() {
|
|
45
|
+
* statusCode = 404
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* const err = new NotFoundError({ id: "123", message: "Not found" });
|
|
49
|
+
* err.statusCode // 404
|
|
50
|
+
* err.report() // works
|
|
51
|
+
*/
|
|
52
|
+
export const TaggedError = Object.assign((tag, BaseClass) => () => {
|
|
53
|
+
const ActualBase = (BaseClass ?? Error);
|
|
54
|
+
class Tagged extends ActualBase {
|
|
55
|
+
_tag = tag;
|
|
56
|
+
/** Type guard for this error class */
|
|
57
|
+
static is(value) {
|
|
58
|
+
return value instanceof Tagged;
|
|
59
|
+
}
|
|
60
|
+
constructor(args) {
|
|
61
|
+
const message = args && 'message' in args && typeof args.message === 'string' ? args.message : undefined;
|
|
62
|
+
const cause = args && 'cause' in args ? args.cause : undefined;
|
|
63
|
+
super(message, cause !== undefined ? { cause } : undefined);
|
|
64
|
+
if (args) {
|
|
65
|
+
Object.assign(this, args);
|
|
66
|
+
}
|
|
67
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
68
|
+
this.name = tag;
|
|
69
|
+
if (cause instanceof Error && cause.stack) {
|
|
70
|
+
const indented = cause.stack.replace(/\n/g, '\n ');
|
|
71
|
+
this.stack = `${this.stack}\nCaused by: ${indented}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
toJSON() {
|
|
75
|
+
return {
|
|
76
|
+
...this,
|
|
77
|
+
_tag: this._tag,
|
|
78
|
+
name: this.name,
|
|
79
|
+
message: this.message,
|
|
80
|
+
cause: serializeCause(this.cause),
|
|
81
|
+
stack: this.stack,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Tagged;
|
|
86
|
+
}, { is: isAnyTaggedError });
|
|
87
|
+
/**
|
|
88
|
+
* Type guard for tagged error instances.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* if (isTaggedError(value)) { value._tag }
|
|
92
|
+
*/
|
|
93
|
+
export const isTaggedError = isAnyTaggedError;
|
|
94
|
+
/**
|
|
95
|
+
* Exhaustive pattern match on error union by _tag.
|
|
96
|
+
* Use `_` handler for plain Error instances without _tag.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Tagged errors only
|
|
100
|
+
* matchError(err, {
|
|
101
|
+
* NotFoundError: (e) => `Missing: ${e.id}`,
|
|
102
|
+
* ValidationError: (e) => `Invalid: ${e.field}`,
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* // Mixed tagged and plain Error
|
|
107
|
+
* matchError(err, {
|
|
108
|
+
* NotFoundError: (e) => `Missing: ${e.id}`,
|
|
109
|
+
* _: (e) => `Unknown error: ${e.message}`,
|
|
110
|
+
* });
|
|
111
|
+
*/
|
|
112
|
+
export function matchError(err, handlers) {
|
|
113
|
+
const h = handlers;
|
|
114
|
+
if ('_tag' in err && typeof err._tag === 'string') {
|
|
115
|
+
const handler = h[err._tag];
|
|
116
|
+
if (handler) {
|
|
117
|
+
return handler(err);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Fall through to _ handler for plain Error
|
|
121
|
+
const fallbackHandler = h['_'];
|
|
122
|
+
if (fallbackHandler) {
|
|
123
|
+
return fallbackHandler(err);
|
|
124
|
+
}
|
|
125
|
+
throw new Error(`No handler for error: ${err.message}`);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Partial pattern match with fallback for unhandled tags.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* matchErrorPartial(err, {
|
|
132
|
+
* NotFoundError: (e) => `Missing: ${e.id}`,
|
|
133
|
+
* }, (e) => `Unknown: ${e.message}`);
|
|
134
|
+
*/
|
|
135
|
+
export function matchErrorPartial(err, handlers, fallback) {
|
|
136
|
+
const h = handlers;
|
|
137
|
+
if ('_tag' in err && typeof err._tag === 'string') {
|
|
138
|
+
const handler = h[err._tag];
|
|
139
|
+
if (handler) {
|
|
140
|
+
return handler(err);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Check for _ handler before fallback
|
|
144
|
+
const underscoreHandler = h['_'];
|
|
145
|
+
if (underscoreHandler) {
|
|
146
|
+
return underscoreHandler(err);
|
|
147
|
+
}
|
|
148
|
+
return fallback(err);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Default error type when catching unknown exceptions.
|
|
152
|
+
*/
|
|
153
|
+
export class UnhandledError extends TaggedError('UnhandledError')() {
|
|
154
|
+
constructor(args) {
|
|
155
|
+
const message = args.cause instanceof Error
|
|
156
|
+
? `Unhandled exception: ${args.cause.message}`
|
|
157
|
+
: `Unhandled exception: ${String(args.cause)}`;
|
|
158
|
+
super({ message, cause: args.cause });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the value or throw if it's an error.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const user = unwrap(result) // throws if result is an error
|
|
6
|
+
* console.log(user.name)
|
|
7
|
+
*
|
|
8
|
+
* @example With custom message
|
|
9
|
+
* const user = unwrap(result, 'Failed to get user')
|
|
10
|
+
*/
|
|
11
|
+
export declare function unwrap<V>(value: V, message?: string): Exclude<V, Error>;
|
|
12
|
+
/**
|
|
13
|
+
* Extract the value or return a fallback if it's an error.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const name = unwrapOr(result, 'Anonymous')
|
|
17
|
+
* // If result is User, returns user
|
|
18
|
+
* // If result is Error, returns 'Anonymous'
|
|
19
|
+
*/
|
|
20
|
+
export declare function unwrapOr<V, U>(value: V, fallback: U): Exclude<V, Error> | U;
|
|
21
|
+
/**
|
|
22
|
+
* Pattern match on an errore value.
|
|
23
|
+
* Handles both success and error cases.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const message = match(result, {
|
|
27
|
+
* ok: user => `Hello, ${user.name}`,
|
|
28
|
+
* err: error => `Failed: ${error.message}`
|
|
29
|
+
* })
|
|
30
|
+
*/
|
|
31
|
+
export declare function match<V, R>(value: V, handlers: {
|
|
32
|
+
ok: (v: Exclude<V, Error>) => R;
|
|
33
|
+
err: (e: Extract<V, Error>) => R;
|
|
34
|
+
}): R;
|
|
35
|
+
/**
|
|
36
|
+
* Partition an array of errore values into [successes, errors].
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const results = await Promise.all(ids.map(fetchUser))
|
|
40
|
+
* const [users, errors] = partition(results)
|
|
41
|
+
*/
|
|
42
|
+
export declare function partition<V>(values: V[]): [Exclude<V, Error>[], Extract<V, Error>[]];
|
|
43
|
+
/**
|
|
44
|
+
* Flatten a nested errore: (E1 | (E2 | T)) becomes (E1 | E2 | T).
|
|
45
|
+
* Useful when chaining operations that can fail.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const nested: NetworkError | (ParseError | User) = await fetchAndParse()
|
|
49
|
+
* const flat: NetworkError | ParseError | User = flatten(nested)
|
|
50
|
+
*/
|
|
51
|
+
export declare function flatten<V>(value: V): V;
|
|
52
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAKvE;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAK3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EACxB,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE;IACR,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;IAC/B,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;CACjC,GACA,CAAC,CAKH;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAWpF;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAEtC"}
|
package/dist/extract.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the value or throw if it's an error.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const user = unwrap(result) // throws if result is an error
|
|
6
|
+
* console.log(user.name)
|
|
7
|
+
*
|
|
8
|
+
* @example With custom message
|
|
9
|
+
* const user = unwrap(result, 'Failed to get user')
|
|
10
|
+
*/
|
|
11
|
+
export function unwrap(value, message) {
|
|
12
|
+
if (value instanceof Error) {
|
|
13
|
+
throw new Error(message ?? `Unwrap called on error: ${value.message}`, { cause: value });
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Extract the value or return a fallback if it's an error.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const name = unwrapOr(result, 'Anonymous')
|
|
22
|
+
* // If result is User, returns user
|
|
23
|
+
* // If result is Error, returns 'Anonymous'
|
|
24
|
+
*/
|
|
25
|
+
export function unwrapOr(value, fallback) {
|
|
26
|
+
if (value instanceof Error) {
|
|
27
|
+
return fallback;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pattern match on an errore value.
|
|
33
|
+
* Handles both success and error cases.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const message = match(result, {
|
|
37
|
+
* ok: user => `Hello, ${user.name}`,
|
|
38
|
+
* err: error => `Failed: ${error.message}`
|
|
39
|
+
* })
|
|
40
|
+
*/
|
|
41
|
+
export function match(value, handlers) {
|
|
42
|
+
if (value instanceof Error) {
|
|
43
|
+
return handlers.err(value);
|
|
44
|
+
}
|
|
45
|
+
return handlers.ok(value);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Partition an array of errore values into [successes, errors].
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const results = await Promise.all(ids.map(fetchUser))
|
|
52
|
+
* const [users, errors] = partition(results)
|
|
53
|
+
*/
|
|
54
|
+
export function partition(values) {
|
|
55
|
+
const oks = [];
|
|
56
|
+
const errs = [];
|
|
57
|
+
for (const v of values) {
|
|
58
|
+
if (v instanceof Error) {
|
|
59
|
+
errs.push(v);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
oks.push(v);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [oks, errs];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Flatten a nested errore: (E1 | (E2 | T)) becomes (E1 | E2 | T).
|
|
69
|
+
* Useful when chaining operations that can fail.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* const nested: NetworkError | (ParseError | User) = await fetchAndParse()
|
|
73
|
+
* const flat: NetworkError | ParseError | User = flatten(nested)
|
|
74
|
+
*/
|
|
75
|
+
export function flatten(value) {
|
|
76
|
+
return value;
|
|
77
|
+
}
|