errore 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # errore
2
+
3
+ Type-safe errors as values for TypeScript. Like Go, but with full type inference.
4
+
5
+ ## Why?
6
+
7
+ Instead of wrapping values in a `Result<T, E>` type, functions simply return `E | T`. TypeScript's type narrowing handles the rest:
8
+
9
+ ```ts
10
+ // Go-style: errors as values
11
+ const user = await fetchUser(id)
12
+ if (isError(user)) return user // TypeScript narrows type
13
+ console.log(user.name) // user is now User, not Error | User
14
+ ```
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ npm install errore
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```ts
25
+ import { tryAsync, isError, TaggedError, matchError } from 'errore'
26
+
27
+ // Define typed errors
28
+ class NotFoundError extends TaggedError('NotFoundError')<{
29
+ id: string
30
+ message: string
31
+ }>() {
32
+ constructor(args: { id: string }) {
33
+ super({ ...args, message: `User ${args.id} not found` })
34
+ }
35
+ }
36
+
37
+ class DbError extends TaggedError('DbError')<{
38
+ message: string
39
+ cause: unknown
40
+ }>() {}
41
+
42
+ // Function returns Error | Value (no wrapper!)
43
+ async function getUser(id: string): Promise<NotFoundError | DbError | User> {
44
+ const result = await tryAsync({
45
+ try: () => db.query(id),
46
+ catch: e => new DbError({ message: 'Query failed', cause: e })
47
+ })
48
+
49
+ if (isError(result)) return result
50
+ if (!result) return new NotFoundError({ id })
51
+
52
+ return result
53
+ }
54
+
55
+ // Caller handles errors explicitly
56
+ const user = await getUser('123')
57
+
58
+ if (isError(user)) {
59
+ matchError(user, {
60
+ NotFoundError: e => console.log(`User ${e.id} not found`),
61
+ DbError: e => console.log(`Database error: ${e.message}`)
62
+ })
63
+ return
64
+ }
65
+
66
+ // TypeScript knows: user is User
67
+ console.log(user.name)
68
+ ```
69
+
70
+ ## API
71
+
72
+ ### Type Guards
73
+
74
+ ```ts
75
+ import { isError, isOk } from 'errore'
76
+
77
+ const result: NetworkError | User = await fetchUser(id)
78
+
79
+ if (isError(result)) {
80
+ // result is NetworkError
81
+ return result
82
+ }
83
+ // result is User
84
+ ```
85
+
86
+ ### Try Functions
87
+
88
+ ```ts
89
+ import { tryFn, tryAsync } from 'errore'
90
+
91
+ // Sync - wraps exceptions in UnhandledError
92
+ const parsed = tryFn(() => JSON.parse(input))
93
+
94
+ // Sync - with custom error type
95
+ const parsed = tryFn({
96
+ try: () => JSON.parse(input),
97
+ catch: e => new ParseError({ cause: e })
98
+ })
99
+
100
+ // Async
101
+ const response = await tryAsync(() => fetch(url))
102
+
103
+ // Async - with custom error
104
+ const response = await tryAsync({
105
+ try: () => fetch(url),
106
+ catch: e => new NetworkError({ cause: e })
107
+ })
108
+ ```
109
+
110
+ ### Transformations
111
+
112
+ ```ts
113
+ import { map, mapError, andThen, tap } from 'errore'
114
+
115
+ // Transform value (if not error)
116
+ const name = map(user, u => u.name)
117
+
118
+ // Transform error
119
+ const appError = mapError(dbError, e => new AppError({ cause: e }))
120
+
121
+ // Chain operations
122
+ const posts = andThen(user, u => fetchPosts(u.id))
123
+
124
+ // Side effects
125
+ const logged = tap(user, u => console.log('Got user:', u.name))
126
+ ```
127
+
128
+ ### Extraction
129
+
130
+ ```ts
131
+ import { unwrap, unwrapOr, match, partition } from 'errore'
132
+
133
+ // Extract or throw
134
+ const user = unwrap(result)
135
+ const user = unwrap(result, 'Custom error message')
136
+
137
+ // Extract or fallback
138
+ const name = unwrapOr(result, 'Anonymous')
139
+
140
+ // Pattern match
141
+ const message = match(result, {
142
+ ok: user => `Hello, ${user.name}`,
143
+ err: error => `Failed: ${error.message}`
144
+ })
145
+
146
+ // Split array into [successes, errors]
147
+ const [users, errors] = partition(results)
148
+ ```
149
+
150
+ ### Tagged Errors
151
+
152
+ ```ts
153
+ import { TaggedError, matchError, matchErrorPartial } from 'errore'
154
+
155
+ // Define errors with _tag discriminant
156
+ class ValidationError extends TaggedError('ValidationError')<{
157
+ field: string
158
+ message: string
159
+ }>() {}
160
+
161
+ class NetworkError extends TaggedError('NetworkError')<{
162
+ url: string
163
+ message: string
164
+ }>() {}
165
+
166
+ type AppError = ValidationError | NetworkError
167
+
168
+ // Exhaustive matching (TypeScript ensures all cases handled)
169
+ matchError(error, {
170
+ ValidationError: e => `Invalid ${e.field}`,
171
+ NetworkError: e => `Failed to fetch ${e.url}`
172
+ })
173
+
174
+ // Partial matching with fallback
175
+ matchErrorPartial(error, {
176
+ ValidationError: e => `Invalid ${e.field}`
177
+ }, e => `Unknown error: ${e.message}`)
178
+
179
+ // Type guards
180
+ ValidationError.is(value) // specific class
181
+ TaggedError.is(value) // any tagged error
182
+ ```
183
+
184
+ ## How Type Safety Works
185
+
186
+ TypeScript narrows types after `instanceof Error` checks:
187
+
188
+ ```ts
189
+ function example(result: NetworkError | User): string {
190
+ if (result instanceof Error) {
191
+ // TypeScript knows: result is NetworkError
192
+ return result.message
193
+ }
194
+ // TypeScript knows: result is User (Error excluded)
195
+ return result.name
196
+ }
197
+ ```
198
+
199
+ This works because:
200
+ 1. `Error` is a built-in class TypeScript understands
201
+ 2. Custom error classes extend `Error`
202
+ 3. After an `instanceof Error` check, TS excludes all Error subtypes
203
+
204
+ ## Comparison with Result Types
205
+
206
+ | Result Pattern | errore |
207
+ |---------------|--------|
208
+ | `Result.ok(value)` | just `return value` |
209
+ | `Result.err(error)` | just `return error` |
210
+ | `result.value` | direct access after guard |
211
+ | `result.map(fn)` | `map(result, fn)` |
212
+ | `Result<User, Error>` | `Error \| User` |
213
+
214
+ ## License
215
+
216
+ MIT
package/dist/index.d.mts CHANGED
@@ -1,3 +1,291 @@
1
- declare const version = "0.0.1";
1
+ /**
2
+ * The core type: either an Error or a value T.
3
+ * Unlike Result<T, E>, this is just a union - no wrapper needed.
4
+ */
5
+ type Errore<T, E extends Error = Error> = E | T;
6
+ /**
7
+ * Extract the error type from an Errore union.
8
+ * @example InferError<NetworkError | User> // NetworkError
9
+ */
10
+ type InferError<T> = T extends Error ? T : never;
11
+ /**
12
+ * Extract the value type from an Errore union.
13
+ * @example InferValue<NetworkError | User> // User
14
+ */
15
+ type InferValue<T> = T extends Error ? never : T;
16
+ /**
17
+ * Utility to ensure T is not an Error type.
18
+ * Used to prevent ambiguous unions like Error | Error.
19
+ */
20
+ type EnsureNotError<T> = T extends Error ? 'Error: Value type T cannot extend Error - this would make the union ambiguous' : T;
2
21
 
3
- export { version };
22
+ /**
23
+ * Any tagged error (for generic constraints)
24
+ */
25
+ type AnyTaggedError = Error & {
26
+ readonly _tag: string;
27
+ };
28
+ /**
29
+ * Instance type produced by TaggedError factory
30
+ */
31
+ type TaggedErrorInstance<Tag extends string, Props> = Error & {
32
+ readonly _tag: Tag;
33
+ toJSON(): object;
34
+ } & Readonly<Props>;
35
+ /**
36
+ * Class type produced by TaggedError factory
37
+ */
38
+ type TaggedErrorClass<Tag extends string, Props> = {
39
+ new (...args: keyof Props extends never ? [args?: {}] : [args: Props]): TaggedErrorInstance<Tag, Props>;
40
+ /** Type guard for this error class */
41
+ is(value: unknown): value is TaggedErrorInstance<Tag, Props>;
42
+ };
43
+ /**
44
+ * Factory for tagged error classes with discriminated _tag property.
45
+ * Enables exhaustive pattern matching on error unions.
46
+ *
47
+ * @example
48
+ * class NotFoundError extends TaggedError("NotFoundError")<{
49
+ * id: string;
50
+ * message: string;
51
+ * }>() {}
52
+ *
53
+ * const err = new NotFoundError({ id: "123", message: "Not found" });
54
+ * err._tag // "NotFoundError"
55
+ * err.id // "123"
56
+ *
57
+ * // Type guard
58
+ * NotFoundError.is(err) // true
59
+ * TaggedError.is(err) // true (any tagged error)
60
+ */
61
+ declare const TaggedError: {
62
+ <Tag extends string>(tag: Tag): <Props extends Record<string, unknown> = {}>() => TaggedErrorClass<Tag, Props>;
63
+ /** Type guard for any TaggedError instance */
64
+ is(value: unknown): value is AnyTaggedError;
65
+ };
66
+ /**
67
+ * Type guard for tagged error instances.
68
+ *
69
+ * @example
70
+ * if (isTaggedError(value)) { value._tag }
71
+ */
72
+ declare const isTaggedError: (value: unknown) => value is AnyTaggedError;
73
+ /**
74
+ * Handler map for exhaustive matching
75
+ */
76
+ type MatchHandlers<E extends AnyTaggedError, R> = {
77
+ [K in E['_tag']]: (err: Extract<E, {
78
+ _tag: K;
79
+ }>) => R;
80
+ };
81
+ /**
82
+ * Exhaustive pattern match on tagged error union by _tag.
83
+ *
84
+ * @example
85
+ * matchError(err, {
86
+ * NotFoundError: (e) => `Missing: ${e.id}`,
87
+ * ValidationError: (e) => `Invalid: ${e.field}`,
88
+ * });
89
+ */
90
+ declare function matchError<E extends AnyTaggedError, R>(err: E, handlers: MatchHandlers<E, R>): R;
91
+ /**
92
+ * Partial pattern match with fallback for unhandled tags.
93
+ *
94
+ * @example
95
+ * matchErrorPartial(err, {
96
+ * NotFoundError: (e) => `Missing: ${e.id}`,
97
+ * }, (e) => `Unknown: ${e.message}`);
98
+ */
99
+ declare function matchErrorPartial<E extends AnyTaggedError, R>(err: E, handlers: Partial<MatchHandlers<E, R>>, fallback: (e: E) => R): R;
100
+ declare const UnhandledError_base: TaggedErrorClass<"UnhandledError", {
101
+ message: string;
102
+ cause: unknown;
103
+ }>;
104
+ /**
105
+ * Default error type when catching unknown exceptions.
106
+ */
107
+ declare class UnhandledError extends UnhandledError_base {
108
+ constructor(args: {
109
+ cause: unknown;
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Type guard: checks if value is an Error.
115
+ * After this check, TypeScript narrows the type to the error types in the union.
116
+ *
117
+ * @example
118
+ * const result = await fetchUser(id)
119
+ * if (isError(result)) {
120
+ * // result is narrowed to the error type
121
+ * return result
122
+ * }
123
+ * // result is narrowed to User
124
+ * console.log(result.name)
125
+ */
126
+ declare function isError<V>(value: V): value is Extract<V, Error>;
127
+ /**
128
+ * Type guard: checks if value is NOT an Error.
129
+ * Inverse of isError for convenience.
130
+ *
131
+ * @example
132
+ * const result = await fetchUser(id)
133
+ * if (isOk(result)) {
134
+ * console.log(result.name) // result is User
135
+ * }
136
+ */
137
+ declare function isOk<V>(value: V): value is Exclude<V, Error>;
138
+ /**
139
+ * Execute a sync function and return either the value or an error.
140
+ *
141
+ * @overload Simple form - wraps exceptions in UnhandledError
142
+ * @example
143
+ * const result = tryFn(() => JSON.parse(input))
144
+ * // result: UnhandledError | unknown
145
+ *
146
+ * @overload With custom catch - you control the error type
147
+ * @example
148
+ * const result = tryFn({
149
+ * try: () => JSON.parse(input),
150
+ * catch: (e) => new ParseError({ cause: e })
151
+ * })
152
+ * // result: ParseError | unknown
153
+ */
154
+ declare function tryFn<T>(fn: () => T): UnhandledError | T;
155
+ declare function tryFn<T, E extends Error>(opts: {
156
+ try: () => T;
157
+ catch: (e: unknown) => E;
158
+ }): E | T;
159
+ /**
160
+ * Execute an async function and return either the value or an error.
161
+ *
162
+ * @overload Simple form - wraps exceptions in UnhandledError
163
+ * @example
164
+ * const result = await tryAsync(() => fetch(url).then(r => r.json()))
165
+ * // result: UnhandledError | unknown
166
+ *
167
+ * @overload With custom catch - you control the error type
168
+ * @example
169
+ * const result = await tryAsync({
170
+ * try: () => fetch(url),
171
+ * catch: (e) => new NetworkError({ cause: e })
172
+ * })
173
+ * // result: NetworkError | Response
174
+ */
175
+ declare function tryAsync<T>(fn: () => Promise<T>): Promise<UnhandledError | T>;
176
+ declare function tryAsync<T, E extends Error>(opts: {
177
+ try: () => Promise<T>;
178
+ catch: (e: unknown) => E | Promise<E>;
179
+ }): Promise<E | T>;
180
+
181
+ /**
182
+ * Transform the value if not an error.
183
+ * If the value is an error, returns it unchanged.
184
+ *
185
+ * @example
186
+ * const result = map(user, u => u.name)
187
+ * // If user is User, result is string
188
+ * // If user is NotFoundError, result is NotFoundError
189
+ */
190
+ declare function map<V, U>(value: V, fn: (v: Exclude<V, Error>) => U): Extract<V, Error> | U;
191
+ /**
192
+ * Transform the error if it is an error.
193
+ * If the value is not an error, returns it unchanged.
194
+ *
195
+ * @example
196
+ * const result = mapError(fetchResult, e => new AppError({ cause: e }))
197
+ * // Converts any error type to AppError
198
+ */
199
+ declare function mapError<V, E2 extends Error>(value: V, fn: (e: Extract<V, Error>) => E2): E2 | Exclude<V, Error>;
200
+ /**
201
+ * Chain another errore-returning function.
202
+ * If the value is an error, returns it unchanged.
203
+ * If successful, runs fn and returns its result.
204
+ *
205
+ * @example
206
+ * const result = andThen(userId, id => fetchUser(id))
207
+ * // If userId is ValidationError, result is ValidationError
208
+ * // If userId is string, result is whatever fetchUser returns
209
+ */
210
+ declare function andThen<V, R>(value: V, fn: (v: Exclude<V, Error>) => R): Extract<V, Error> | R;
211
+ /**
212
+ * Async version of andThen.
213
+ *
214
+ * @example
215
+ * const result = await andThenAsync(userId, async id => {
216
+ * const user = await fetchUser(id)
217
+ * return user
218
+ * })
219
+ */
220
+ declare function andThenAsync<V, R>(value: V, fn: (v: Exclude<V, Error>) => Promise<R>): Promise<Extract<V, Error> | R>;
221
+ /**
222
+ * Run a side effect if the value is not an error.
223
+ * Returns the original value unchanged.
224
+ *
225
+ * @example
226
+ * const result = tap(user, u => console.log('Got user:', u.name))
227
+ */
228
+ declare function tap<V>(value: V, fn: (v: Exclude<V, Error>) => void): V;
229
+ /**
230
+ * Async version of tap.
231
+ *
232
+ * @example
233
+ * const result = await tapAsync(user, async u => {
234
+ * await logToService(u)
235
+ * })
236
+ */
237
+ declare function tapAsync<V>(value: V, fn: (v: Exclude<V, Error>) => Promise<void>): Promise<V>;
238
+
239
+ /**
240
+ * Extract the value or throw if it's an error.
241
+ *
242
+ * @example
243
+ * const user = unwrap(result) // throws if result is an error
244
+ * console.log(user.name)
245
+ *
246
+ * @example With custom message
247
+ * const user = unwrap(result, 'Failed to get user')
248
+ */
249
+ declare function unwrap<V>(value: V, message?: string): Exclude<V, Error>;
250
+ /**
251
+ * Extract the value or return a fallback if it's an error.
252
+ *
253
+ * @example
254
+ * const name = unwrapOr(result, 'Anonymous')
255
+ * // If result is User, returns user
256
+ * // If result is Error, returns 'Anonymous'
257
+ */
258
+ declare function unwrapOr<V, U>(value: V, fallback: U): Exclude<V, Error> | U;
259
+ /**
260
+ * Pattern match on an errore value.
261
+ * Handles both success and error cases.
262
+ *
263
+ * @example
264
+ * const message = match(result, {
265
+ * ok: user => `Hello, ${user.name}`,
266
+ * err: error => `Failed: ${error.message}`
267
+ * })
268
+ */
269
+ declare function match<V, R>(value: V, handlers: {
270
+ ok: (v: Exclude<V, Error>) => R;
271
+ err: (e: Extract<V, Error>) => R;
272
+ }): R;
273
+ /**
274
+ * Partition an array of errore values into [successes, errors].
275
+ *
276
+ * @example
277
+ * const results = await Promise.all(ids.map(fetchUser))
278
+ * const [users, errors] = partition(results)
279
+ */
280
+ declare function partition<V>(values: V[]): [Exclude<V, Error>[], Extract<V, Error>[]];
281
+ /**
282
+ * Flatten a nested errore: (E1 | (E2 | T)) becomes (E1 | E2 | T).
283
+ * Useful when chaining operations that can fail.
284
+ *
285
+ * @example
286
+ * const nested: NetworkError | (ParseError | User) = await fetchAndParse()
287
+ * const flat: NetworkError | ParseError | User = flatten(nested)
288
+ */
289
+ declare function flatten<V>(value: V): V;
290
+
291
+ export { type EnsureNotError, type Errore, type InferError, type InferValue, TaggedError, type TaggedErrorClass, type TaggedErrorInstance, UnhandledError, andThen, andThenAsync, flatten, isError, isOk, isTaggedError, map, mapError, match, matchError, matchErrorPartial, partition, tap, tapAsync, tryAsync, tryFn, unwrap, unwrapOr };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,291 @@
1
- declare const version = "0.0.1";
1
+ /**
2
+ * The core type: either an Error or a value T.
3
+ * Unlike Result<T, E>, this is just a union - no wrapper needed.
4
+ */
5
+ type Errore<T, E extends Error = Error> = E | T;
6
+ /**
7
+ * Extract the error type from an Errore union.
8
+ * @example InferError<NetworkError | User> // NetworkError
9
+ */
10
+ type InferError<T> = T extends Error ? T : never;
11
+ /**
12
+ * Extract the value type from an Errore union.
13
+ * @example InferValue<NetworkError | User> // User
14
+ */
15
+ type InferValue<T> = T extends Error ? never : T;
16
+ /**
17
+ * Utility to ensure T is not an Error type.
18
+ * Used to prevent ambiguous unions like Error | Error.
19
+ */
20
+ type EnsureNotError<T> = T extends Error ? 'Error: Value type T cannot extend Error - this would make the union ambiguous' : T;
2
21
 
3
- export { version };
22
+ /**
23
+ * Any tagged error (for generic constraints)
24
+ */
25
+ type AnyTaggedError = Error & {
26
+ readonly _tag: string;
27
+ };
28
+ /**
29
+ * Instance type produced by TaggedError factory
30
+ */
31
+ type TaggedErrorInstance<Tag extends string, Props> = Error & {
32
+ readonly _tag: Tag;
33
+ toJSON(): object;
34
+ } & Readonly<Props>;
35
+ /**
36
+ * Class type produced by TaggedError factory
37
+ */
38
+ type TaggedErrorClass<Tag extends string, Props> = {
39
+ new (...args: keyof Props extends never ? [args?: {}] : [args: Props]): TaggedErrorInstance<Tag, Props>;
40
+ /** Type guard for this error class */
41
+ is(value: unknown): value is TaggedErrorInstance<Tag, Props>;
42
+ };
43
+ /**
44
+ * Factory for tagged error classes with discriminated _tag property.
45
+ * Enables exhaustive pattern matching on error unions.
46
+ *
47
+ * @example
48
+ * class NotFoundError extends TaggedError("NotFoundError")<{
49
+ * id: string;
50
+ * message: string;
51
+ * }>() {}
52
+ *
53
+ * const err = new NotFoundError({ id: "123", message: "Not found" });
54
+ * err._tag // "NotFoundError"
55
+ * err.id // "123"
56
+ *
57
+ * // Type guard
58
+ * NotFoundError.is(err) // true
59
+ * TaggedError.is(err) // true (any tagged error)
60
+ */
61
+ declare const TaggedError: {
62
+ <Tag extends string>(tag: Tag): <Props extends Record<string, unknown> = {}>() => TaggedErrorClass<Tag, Props>;
63
+ /** Type guard for any TaggedError instance */
64
+ is(value: unknown): value is AnyTaggedError;
65
+ };
66
+ /**
67
+ * Type guard for tagged error instances.
68
+ *
69
+ * @example
70
+ * if (isTaggedError(value)) { value._tag }
71
+ */
72
+ declare const isTaggedError: (value: unknown) => value is AnyTaggedError;
73
+ /**
74
+ * Handler map for exhaustive matching
75
+ */
76
+ type MatchHandlers<E extends AnyTaggedError, R> = {
77
+ [K in E['_tag']]: (err: Extract<E, {
78
+ _tag: K;
79
+ }>) => R;
80
+ };
81
+ /**
82
+ * Exhaustive pattern match on tagged error union by _tag.
83
+ *
84
+ * @example
85
+ * matchError(err, {
86
+ * NotFoundError: (e) => `Missing: ${e.id}`,
87
+ * ValidationError: (e) => `Invalid: ${e.field}`,
88
+ * });
89
+ */
90
+ declare function matchError<E extends AnyTaggedError, R>(err: E, handlers: MatchHandlers<E, R>): R;
91
+ /**
92
+ * Partial pattern match with fallback for unhandled tags.
93
+ *
94
+ * @example
95
+ * matchErrorPartial(err, {
96
+ * NotFoundError: (e) => `Missing: ${e.id}`,
97
+ * }, (e) => `Unknown: ${e.message}`);
98
+ */
99
+ declare function matchErrorPartial<E extends AnyTaggedError, R>(err: E, handlers: Partial<MatchHandlers<E, R>>, fallback: (e: E) => R): R;
100
+ declare const UnhandledError_base: TaggedErrorClass<"UnhandledError", {
101
+ message: string;
102
+ cause: unknown;
103
+ }>;
104
+ /**
105
+ * Default error type when catching unknown exceptions.
106
+ */
107
+ declare class UnhandledError extends UnhandledError_base {
108
+ constructor(args: {
109
+ cause: unknown;
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Type guard: checks if value is an Error.
115
+ * After this check, TypeScript narrows the type to the error types in the union.
116
+ *
117
+ * @example
118
+ * const result = await fetchUser(id)
119
+ * if (isError(result)) {
120
+ * // result is narrowed to the error type
121
+ * return result
122
+ * }
123
+ * // result is narrowed to User
124
+ * console.log(result.name)
125
+ */
126
+ declare function isError<V>(value: V): value is Extract<V, Error>;
127
+ /**
128
+ * Type guard: checks if value is NOT an Error.
129
+ * Inverse of isError for convenience.
130
+ *
131
+ * @example
132
+ * const result = await fetchUser(id)
133
+ * if (isOk(result)) {
134
+ * console.log(result.name) // result is User
135
+ * }
136
+ */
137
+ declare function isOk<V>(value: V): value is Exclude<V, Error>;
138
+ /**
139
+ * Execute a sync function and return either the value or an error.
140
+ *
141
+ * @overload Simple form - wraps exceptions in UnhandledError
142
+ * @example
143
+ * const result = tryFn(() => JSON.parse(input))
144
+ * // result: UnhandledError | unknown
145
+ *
146
+ * @overload With custom catch - you control the error type
147
+ * @example
148
+ * const result = tryFn({
149
+ * try: () => JSON.parse(input),
150
+ * catch: (e) => new ParseError({ cause: e })
151
+ * })
152
+ * // result: ParseError | unknown
153
+ */
154
+ declare function tryFn<T>(fn: () => T): UnhandledError | T;
155
+ declare function tryFn<T, E extends Error>(opts: {
156
+ try: () => T;
157
+ catch: (e: unknown) => E;
158
+ }): E | T;
159
+ /**
160
+ * Execute an async function and return either the value or an error.
161
+ *
162
+ * @overload Simple form - wraps exceptions in UnhandledError
163
+ * @example
164
+ * const result = await tryAsync(() => fetch(url).then(r => r.json()))
165
+ * // result: UnhandledError | unknown
166
+ *
167
+ * @overload With custom catch - you control the error type
168
+ * @example
169
+ * const result = await tryAsync({
170
+ * try: () => fetch(url),
171
+ * catch: (e) => new NetworkError({ cause: e })
172
+ * })
173
+ * // result: NetworkError | Response
174
+ */
175
+ declare function tryAsync<T>(fn: () => Promise<T>): Promise<UnhandledError | T>;
176
+ declare function tryAsync<T, E extends Error>(opts: {
177
+ try: () => Promise<T>;
178
+ catch: (e: unknown) => E | Promise<E>;
179
+ }): Promise<E | T>;
180
+
181
+ /**
182
+ * Transform the value if not an error.
183
+ * If the value is an error, returns it unchanged.
184
+ *
185
+ * @example
186
+ * const result = map(user, u => u.name)
187
+ * // If user is User, result is string
188
+ * // If user is NotFoundError, result is NotFoundError
189
+ */
190
+ declare function map<V, U>(value: V, fn: (v: Exclude<V, Error>) => U): Extract<V, Error> | U;
191
+ /**
192
+ * Transform the error if it is an error.
193
+ * If the value is not an error, returns it unchanged.
194
+ *
195
+ * @example
196
+ * const result = mapError(fetchResult, e => new AppError({ cause: e }))
197
+ * // Converts any error type to AppError
198
+ */
199
+ declare function mapError<V, E2 extends Error>(value: V, fn: (e: Extract<V, Error>) => E2): E2 | Exclude<V, Error>;
200
+ /**
201
+ * Chain another errore-returning function.
202
+ * If the value is an error, returns it unchanged.
203
+ * If successful, runs fn and returns its result.
204
+ *
205
+ * @example
206
+ * const result = andThen(userId, id => fetchUser(id))
207
+ * // If userId is ValidationError, result is ValidationError
208
+ * // If userId is string, result is whatever fetchUser returns
209
+ */
210
+ declare function andThen<V, R>(value: V, fn: (v: Exclude<V, Error>) => R): Extract<V, Error> | R;
211
+ /**
212
+ * Async version of andThen.
213
+ *
214
+ * @example
215
+ * const result = await andThenAsync(userId, async id => {
216
+ * const user = await fetchUser(id)
217
+ * return user
218
+ * })
219
+ */
220
+ declare function andThenAsync<V, R>(value: V, fn: (v: Exclude<V, Error>) => Promise<R>): Promise<Extract<V, Error> | R>;
221
+ /**
222
+ * Run a side effect if the value is not an error.
223
+ * Returns the original value unchanged.
224
+ *
225
+ * @example
226
+ * const result = tap(user, u => console.log('Got user:', u.name))
227
+ */
228
+ declare function tap<V>(value: V, fn: (v: Exclude<V, Error>) => void): V;
229
+ /**
230
+ * Async version of tap.
231
+ *
232
+ * @example
233
+ * const result = await tapAsync(user, async u => {
234
+ * await logToService(u)
235
+ * })
236
+ */
237
+ declare function tapAsync<V>(value: V, fn: (v: Exclude<V, Error>) => Promise<void>): Promise<V>;
238
+
239
+ /**
240
+ * Extract the value or throw if it's an error.
241
+ *
242
+ * @example
243
+ * const user = unwrap(result) // throws if result is an error
244
+ * console.log(user.name)
245
+ *
246
+ * @example With custom message
247
+ * const user = unwrap(result, 'Failed to get user')
248
+ */
249
+ declare function unwrap<V>(value: V, message?: string): Exclude<V, Error>;
250
+ /**
251
+ * Extract the value or return a fallback if it's an error.
252
+ *
253
+ * @example
254
+ * const name = unwrapOr(result, 'Anonymous')
255
+ * // If result is User, returns user
256
+ * // If result is Error, returns 'Anonymous'
257
+ */
258
+ declare function unwrapOr<V, U>(value: V, fallback: U): Exclude<V, Error> | U;
259
+ /**
260
+ * Pattern match on an errore value.
261
+ * Handles both success and error cases.
262
+ *
263
+ * @example
264
+ * const message = match(result, {
265
+ * ok: user => `Hello, ${user.name}`,
266
+ * err: error => `Failed: ${error.message}`
267
+ * })
268
+ */
269
+ declare function match<V, R>(value: V, handlers: {
270
+ ok: (v: Exclude<V, Error>) => R;
271
+ err: (e: Extract<V, Error>) => R;
272
+ }): R;
273
+ /**
274
+ * Partition an array of errore values into [successes, errors].
275
+ *
276
+ * @example
277
+ * const results = await Promise.all(ids.map(fetchUser))
278
+ * const [users, errors] = partition(results)
279
+ */
280
+ declare function partition<V>(values: V[]): [Exclude<V, Error>[], Extract<V, Error>[]];
281
+ /**
282
+ * Flatten a nested errore: (E1 | (E2 | T)) becomes (E1 | E2 | T).
283
+ * Useful when chaining operations that can fail.
284
+ *
285
+ * @example
286
+ * const nested: NetworkError | (ParseError | User) = await fetchAndParse()
287
+ * const flat: NetworkError | ParseError | User = flatten(nested)
288
+ */
289
+ declare function flatten<V>(value: V): V;
290
+
291
+ export { type EnsureNotError, type Errore, type InferError, type InferValue, TaggedError, type TaggedErrorClass, type TaggedErrorInstance, UnhandledError, andThen, andThenAsync, flatten, isError, isOk, isTaggedError, map, mapError, match, matchError, matchErrorPartial, partition, tap, tapAsync, tryAsync, tryFn, unwrap, unwrapOr };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -19,11 +20,224 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
20
  // src/index.ts
20
21
  var index_exports = {};
21
22
  __export(index_exports, {
22
- version: () => version
23
+ TaggedError: () => TaggedError,
24
+ UnhandledError: () => UnhandledError,
25
+ andThen: () => andThen,
26
+ andThenAsync: () => andThenAsync,
27
+ flatten: () => flatten,
28
+ isError: () => isError,
29
+ isOk: () => isOk,
30
+ isTaggedError: () => isTaggedError,
31
+ map: () => map,
32
+ mapError: () => mapError,
33
+ match: () => match,
34
+ matchError: () => matchError,
35
+ matchErrorPartial: () => matchErrorPartial,
36
+ partition: () => partition,
37
+ tap: () => tap,
38
+ tapAsync: () => tapAsync,
39
+ tryAsync: () => tryAsync,
40
+ tryFn: () => tryFn,
41
+ unwrap: () => unwrap,
42
+ unwrapOr: () => unwrapOr
23
43
  });
24
44
  module.exports = __toCommonJS(index_exports);
25
- var version = "0.0.1";
45
+
46
+ // src/error.ts
47
+ var serializeCause = (cause) => {
48
+ if (cause instanceof Error) {
49
+ return { name: cause.name, message: cause.message, stack: cause.stack };
50
+ }
51
+ return cause;
52
+ };
53
+ var isAnyTaggedError = (value) => {
54
+ return value instanceof Error && "_tag" in value && typeof value._tag === "string";
55
+ };
56
+ var TaggedError = Object.assign(
57
+ (tag) => () => {
58
+ class Base extends Error {
59
+ _tag = tag;
60
+ /** Type guard for this error class */
61
+ static is(value) {
62
+ return value instanceof Base;
63
+ }
64
+ constructor(args) {
65
+ const message = args && "message" in args && typeof args.message === "string" ? args.message : void 0;
66
+ const cause = args && "cause" in args ? args.cause : void 0;
67
+ super(message, cause !== void 0 ? { cause } : void 0);
68
+ if (args) {
69
+ Object.assign(this, args);
70
+ }
71
+ Object.setPrototypeOf(this, new.target.prototype);
72
+ this.name = tag;
73
+ if (cause instanceof Error && cause.stack) {
74
+ const indented = cause.stack.replace(/\n/g, "\n ");
75
+ this.stack = `${this.stack}
76
+ Caused by: ${indented}`;
77
+ }
78
+ }
79
+ toJSON() {
80
+ return {
81
+ ...this,
82
+ _tag: this._tag,
83
+ name: this.name,
84
+ message: this.message,
85
+ cause: serializeCause(this.cause),
86
+ stack: this.stack
87
+ };
88
+ }
89
+ }
90
+ return Base;
91
+ },
92
+ { is: isAnyTaggedError }
93
+ );
94
+ var isTaggedError = isAnyTaggedError;
95
+ function matchError(err, handlers) {
96
+ const handler = handlers[err._tag];
97
+ return handler(err);
98
+ }
99
+ function matchErrorPartial(err, handlers, fallback) {
100
+ const handler = handlers[err._tag];
101
+ if (handler) {
102
+ return handler(err);
103
+ }
104
+ return fallback(err);
105
+ }
106
+ var UnhandledError = class extends TaggedError("UnhandledError")() {
107
+ constructor(args) {
108
+ const message = args.cause instanceof Error ? `Unhandled exception: ${args.cause.message}` : `Unhandled exception: ${String(args.cause)}`;
109
+ super({ message, cause: args.cause });
110
+ }
111
+ };
112
+
113
+ // src/core.ts
114
+ function isError(value) {
115
+ return value instanceof Error;
116
+ }
117
+ function isOk(value) {
118
+ return !(value instanceof Error);
119
+ }
120
+ function tryFn(fnOrOpts) {
121
+ if (typeof fnOrOpts === "function") {
122
+ try {
123
+ return fnOrOpts();
124
+ } catch (cause) {
125
+ return new UnhandledError({ cause });
126
+ }
127
+ }
128
+ try {
129
+ return fnOrOpts.try();
130
+ } catch (cause) {
131
+ return fnOrOpts.catch(cause);
132
+ }
133
+ }
134
+ async function tryAsync(fnOrOpts) {
135
+ if (typeof fnOrOpts === "function") {
136
+ try {
137
+ return await fnOrOpts();
138
+ } catch (cause) {
139
+ return new UnhandledError({ cause });
140
+ }
141
+ }
142
+ try {
143
+ return await fnOrOpts.try();
144
+ } catch (cause) {
145
+ return await fnOrOpts.catch(cause);
146
+ }
147
+ }
148
+
149
+ // src/transform.ts
150
+ function map(value, fn) {
151
+ if (value instanceof Error) {
152
+ return value;
153
+ }
154
+ return fn(value);
155
+ }
156
+ function mapError(value, fn) {
157
+ if (value instanceof Error) {
158
+ return fn(value);
159
+ }
160
+ return value;
161
+ }
162
+ function andThen(value, fn) {
163
+ if (value instanceof Error) {
164
+ return value;
165
+ }
166
+ return fn(value);
167
+ }
168
+ async function andThenAsync(value, fn) {
169
+ if (value instanceof Error) {
170
+ return value;
171
+ }
172
+ return fn(value);
173
+ }
174
+ function tap(value, fn) {
175
+ if (!(value instanceof Error)) {
176
+ fn(value);
177
+ }
178
+ return value;
179
+ }
180
+ async function tapAsync(value, fn) {
181
+ if (!(value instanceof Error)) {
182
+ await fn(value);
183
+ }
184
+ return value;
185
+ }
186
+
187
+ // src/extract.ts
188
+ function unwrap(value, message) {
189
+ if (value instanceof Error) {
190
+ throw new Error(message ?? `Unwrap called on error: ${value.message}`, { cause: value });
191
+ }
192
+ return value;
193
+ }
194
+ function unwrapOr(value, fallback) {
195
+ if (value instanceof Error) {
196
+ return fallback;
197
+ }
198
+ return value;
199
+ }
200
+ function match(value, handlers) {
201
+ if (value instanceof Error) {
202
+ return handlers.err(value);
203
+ }
204
+ return handlers.ok(value);
205
+ }
206
+ function partition(values) {
207
+ const oks = [];
208
+ const errs = [];
209
+ for (const v of values) {
210
+ if (v instanceof Error) {
211
+ errs.push(v);
212
+ } else {
213
+ oks.push(v);
214
+ }
215
+ }
216
+ return [oks, errs];
217
+ }
218
+ function flatten(value) {
219
+ return value;
220
+ }
26
221
  // Annotate the CommonJS export names for ESM import in node:
27
222
  0 && (module.exports = {
28
- version
223
+ TaggedError,
224
+ UnhandledError,
225
+ andThen,
226
+ andThenAsync,
227
+ flatten,
228
+ isError,
229
+ isOk,
230
+ isTaggedError,
231
+ map,
232
+ mapError,
233
+ match,
234
+ matchError,
235
+ matchErrorPartial,
236
+ partition,
237
+ tap,
238
+ tapAsync,
239
+ tryAsync,
240
+ tryFn,
241
+ unwrap,
242
+ unwrapOr
29
243
  });
package/dist/index.mjs CHANGED
@@ -1,5 +1,197 @@
1
- // src/index.ts
2
- var version = "0.0.1";
1
+ // src/error.ts
2
+ var serializeCause = (cause) => {
3
+ if (cause instanceof Error) {
4
+ return { name: cause.name, message: cause.message, stack: cause.stack };
5
+ }
6
+ return cause;
7
+ };
8
+ var isAnyTaggedError = (value) => {
9
+ return value instanceof Error && "_tag" in value && typeof value._tag === "string";
10
+ };
11
+ var TaggedError = Object.assign(
12
+ (tag) => () => {
13
+ class Base extends Error {
14
+ _tag = tag;
15
+ /** Type guard for this error class */
16
+ static is(value) {
17
+ return value instanceof Base;
18
+ }
19
+ constructor(args) {
20
+ const message = args && "message" in args && typeof args.message === "string" ? args.message : void 0;
21
+ const cause = args && "cause" in args ? args.cause : void 0;
22
+ super(message, cause !== void 0 ? { cause } : void 0);
23
+ if (args) {
24
+ Object.assign(this, args);
25
+ }
26
+ Object.setPrototypeOf(this, new.target.prototype);
27
+ this.name = tag;
28
+ if (cause instanceof Error && cause.stack) {
29
+ const indented = cause.stack.replace(/\n/g, "\n ");
30
+ this.stack = `${this.stack}
31
+ Caused by: ${indented}`;
32
+ }
33
+ }
34
+ toJSON() {
35
+ return {
36
+ ...this,
37
+ _tag: this._tag,
38
+ name: this.name,
39
+ message: this.message,
40
+ cause: serializeCause(this.cause),
41
+ stack: this.stack
42
+ };
43
+ }
44
+ }
45
+ return Base;
46
+ },
47
+ { is: isAnyTaggedError }
48
+ );
49
+ var isTaggedError = isAnyTaggedError;
50
+ function matchError(err, handlers) {
51
+ const handler = handlers[err._tag];
52
+ return handler(err);
53
+ }
54
+ function matchErrorPartial(err, handlers, fallback) {
55
+ const handler = handlers[err._tag];
56
+ if (handler) {
57
+ return handler(err);
58
+ }
59
+ return fallback(err);
60
+ }
61
+ var UnhandledError = class extends TaggedError("UnhandledError")() {
62
+ constructor(args) {
63
+ const message = args.cause instanceof Error ? `Unhandled exception: ${args.cause.message}` : `Unhandled exception: ${String(args.cause)}`;
64
+ super({ message, cause: args.cause });
65
+ }
66
+ };
67
+
68
+ // src/core.ts
69
+ function isError(value) {
70
+ return value instanceof Error;
71
+ }
72
+ function isOk(value) {
73
+ return !(value instanceof Error);
74
+ }
75
+ function tryFn(fnOrOpts) {
76
+ if (typeof fnOrOpts === "function") {
77
+ try {
78
+ return fnOrOpts();
79
+ } catch (cause) {
80
+ return new UnhandledError({ cause });
81
+ }
82
+ }
83
+ try {
84
+ return fnOrOpts.try();
85
+ } catch (cause) {
86
+ return fnOrOpts.catch(cause);
87
+ }
88
+ }
89
+ async function tryAsync(fnOrOpts) {
90
+ if (typeof fnOrOpts === "function") {
91
+ try {
92
+ return await fnOrOpts();
93
+ } catch (cause) {
94
+ return new UnhandledError({ cause });
95
+ }
96
+ }
97
+ try {
98
+ return await fnOrOpts.try();
99
+ } catch (cause) {
100
+ return await fnOrOpts.catch(cause);
101
+ }
102
+ }
103
+
104
+ // src/transform.ts
105
+ function map(value, fn) {
106
+ if (value instanceof Error) {
107
+ return value;
108
+ }
109
+ return fn(value);
110
+ }
111
+ function mapError(value, fn) {
112
+ if (value instanceof Error) {
113
+ return fn(value);
114
+ }
115
+ return value;
116
+ }
117
+ function andThen(value, fn) {
118
+ if (value instanceof Error) {
119
+ return value;
120
+ }
121
+ return fn(value);
122
+ }
123
+ async function andThenAsync(value, fn) {
124
+ if (value instanceof Error) {
125
+ return value;
126
+ }
127
+ return fn(value);
128
+ }
129
+ function tap(value, fn) {
130
+ if (!(value instanceof Error)) {
131
+ fn(value);
132
+ }
133
+ return value;
134
+ }
135
+ async function tapAsync(value, fn) {
136
+ if (!(value instanceof Error)) {
137
+ await fn(value);
138
+ }
139
+ return value;
140
+ }
141
+
142
+ // src/extract.ts
143
+ function unwrap(value, message) {
144
+ if (value instanceof Error) {
145
+ throw new Error(message ?? `Unwrap called on error: ${value.message}`, { cause: value });
146
+ }
147
+ return value;
148
+ }
149
+ function unwrapOr(value, fallback) {
150
+ if (value instanceof Error) {
151
+ return fallback;
152
+ }
153
+ return value;
154
+ }
155
+ function match(value, handlers) {
156
+ if (value instanceof Error) {
157
+ return handlers.err(value);
158
+ }
159
+ return handlers.ok(value);
160
+ }
161
+ function partition(values) {
162
+ const oks = [];
163
+ const errs = [];
164
+ for (const v of values) {
165
+ if (v instanceof Error) {
166
+ errs.push(v);
167
+ } else {
168
+ oks.push(v);
169
+ }
170
+ }
171
+ return [oks, errs];
172
+ }
173
+ function flatten(value) {
174
+ return value;
175
+ }
3
176
  export {
4
- version
177
+ TaggedError,
178
+ UnhandledError,
179
+ andThen,
180
+ andThenAsync,
181
+ flatten,
182
+ isError,
183
+ isOk,
184
+ isTaggedError,
185
+ map,
186
+ mapError,
187
+ match,
188
+ matchError,
189
+ matchErrorPartial,
190
+ partition,
191
+ tap,
192
+ tapAsync,
193
+ tryAsync,
194
+ tryFn,
195
+ unwrap,
196
+ unwrapOr
5
197
  };
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "errore",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "Type-safe errors as values for TypeScript. Like Go, but with full type inference.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "types": "./dist/index.d.ts",
10
11
  "import": "./dist/index.mjs",
11
- "require": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
12
+ "require": "./dist/index.js"
13
13
  }
14
14
  },
15
15
  "files": [
@@ -17,7 +17,8 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "build": "tsup src/index.ts --format cjs,esm --dts",
20
- "typecheck": "tsc --noEmit"
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "vitest"
21
22
  },
22
23
  "keywords": [
23
24
  "error",
@@ -31,6 +32,7 @@
31
32
  "license": "MIT",
32
33
  "devDependencies": {
33
34
  "tsup": "^8.0.0",
34
- "typescript": "^5.0.0"
35
+ "typescript": "^5.0.0",
36
+ "vitest": "^3.2.4"
35
37
  }
36
38
  }