errore 0.5.1 → 0.6.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 +93 -46
- package/dist/index.d.mts +28 -6
- package/dist/index.d.ts +28 -6
- package/dist/index.js +5 -4
- package/dist/index.mjs +5 -4
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -22,10 +22,10 @@ npm install errore
|
|
|
22
22
|
## Quick Start
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
|
-
import
|
|
25
|
+
import * as errore from 'errore'
|
|
26
26
|
|
|
27
27
|
// Define typed errors
|
|
28
|
-
class NotFoundError extends TaggedError('NotFoundError')<{
|
|
28
|
+
class NotFoundError extends errore.TaggedError('NotFoundError')<{
|
|
29
29
|
id: string
|
|
30
30
|
message: string
|
|
31
31
|
}>() {
|
|
@@ -34,19 +34,19 @@ class NotFoundError extends TaggedError('NotFoundError')<{
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
class DbError extends TaggedError('DbError')<{
|
|
37
|
+
class DbError extends errore.TaggedError('DbError')<{
|
|
38
38
|
message: string
|
|
39
39
|
cause: unknown
|
|
40
40
|
}>() {}
|
|
41
41
|
|
|
42
42
|
// Function returns Error | Value (no wrapper!)
|
|
43
43
|
async function getUser(id: string): Promise<NotFoundError | DbError | User> {
|
|
44
|
-
const result = await tryAsync({
|
|
44
|
+
const result = await errore.tryAsync({
|
|
45
45
|
try: () => db.query(id),
|
|
46
46
|
catch: e => new DbError({ message: 'Query failed', cause: e })
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
if (isError(result)) return result
|
|
49
|
+
if (errore.isError(result)) return result
|
|
50
50
|
if (!result) return new NotFoundError({ id })
|
|
51
51
|
|
|
52
52
|
return result
|
|
@@ -55,8 +55,8 @@ async function getUser(id: string): Promise<NotFoundError | DbError | User> {
|
|
|
55
55
|
// Caller handles errors explicitly
|
|
56
56
|
const user = await getUser('123')
|
|
57
57
|
|
|
58
|
-
if (isError(user)) {
|
|
59
|
-
matchError(user, {
|
|
58
|
+
if (errore.isError(user)) {
|
|
59
|
+
errore.matchError(user, {
|
|
60
60
|
NotFoundError: e => console.log(`User ${e.id} not found`),
|
|
61
61
|
DbError: e => console.log(`Database error: ${e.message}`)
|
|
62
62
|
})
|
|
@@ -72,11 +72,11 @@ console.log(user.name)
|
|
|
72
72
|
### Type Guards
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
|
-
import
|
|
75
|
+
import * as errore from 'errore'
|
|
76
76
|
|
|
77
77
|
const result: NetworkError | User = await fetchUser(id)
|
|
78
78
|
|
|
79
|
-
if (isError(result)) {
|
|
79
|
+
if (errore.isError(result)) {
|
|
80
80
|
// result is NetworkError
|
|
81
81
|
return result
|
|
82
82
|
}
|
|
@@ -86,22 +86,22 @@ if (isError(result)) {
|
|
|
86
86
|
### Try Functions
|
|
87
87
|
|
|
88
88
|
```ts
|
|
89
|
-
import
|
|
89
|
+
import * as errore from 'errore'
|
|
90
90
|
|
|
91
91
|
// Sync - wraps exceptions in UnhandledError
|
|
92
|
-
const parsed = tryFn(() => JSON.parse(input))
|
|
92
|
+
const parsed = errore.tryFn(() => JSON.parse(input))
|
|
93
93
|
|
|
94
94
|
// Sync - with custom error type
|
|
95
|
-
const parsed = tryFn({
|
|
95
|
+
const parsed = errore.tryFn({
|
|
96
96
|
try: () => JSON.parse(input),
|
|
97
97
|
catch: e => new ParseError({ cause: e })
|
|
98
98
|
})
|
|
99
99
|
|
|
100
100
|
// Async
|
|
101
|
-
const response = await tryAsync(() => fetch(url))
|
|
101
|
+
const response = await errore.tryAsync(() => fetch(url))
|
|
102
102
|
|
|
103
103
|
// Async - with custom error
|
|
104
|
-
const response = await tryAsync({
|
|
104
|
+
const response = await errore.tryAsync({
|
|
105
105
|
try: () => fetch(url),
|
|
106
106
|
catch: e => new NetworkError({ cause: e })
|
|
107
107
|
})
|
|
@@ -110,19 +110,19 @@ const response = await tryAsync({
|
|
|
110
110
|
### Transformations
|
|
111
111
|
|
|
112
112
|
```ts
|
|
113
|
-
import
|
|
113
|
+
import * as errore from 'errore'
|
|
114
114
|
|
|
115
115
|
// Transform value (if not error)
|
|
116
|
-
const name = map(user, u => u.name)
|
|
116
|
+
const name = errore.map(user, u => u.name)
|
|
117
117
|
|
|
118
118
|
// Transform error
|
|
119
|
-
const appError = mapError(dbError, e => new AppError({ cause: e }))
|
|
119
|
+
const appError = errore.mapError(dbError, e => new AppError({ cause: e }))
|
|
120
120
|
|
|
121
121
|
// Chain operations
|
|
122
|
-
const posts = andThen(user, u => fetchPosts(u.id))
|
|
122
|
+
const posts = errore.andThen(user, u => fetchPosts(u.id))
|
|
123
123
|
|
|
124
124
|
// Side effects
|
|
125
|
-
const logged = tap(user, u => console.log('Got user:', u.name))
|
|
125
|
+
const logged = errore.tap(user, u => console.log('Got user:', u.name))
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
### Composing Operations
|
|
@@ -130,7 +130,7 @@ const logged = tap(user, u => console.log('Got user:', u.name))
|
|
|
130
130
|
Chain multiple operations together:
|
|
131
131
|
|
|
132
132
|
```ts
|
|
133
|
-
import
|
|
133
|
+
import * as errore from 'errore'
|
|
134
134
|
|
|
135
135
|
// Define operations that can fail
|
|
136
136
|
function parseNumber(s: string): ValidationError | number { ... }
|
|
@@ -138,24 +138,24 @@ function validatePositive(n: number): ValidationError | number { ... }
|
|
|
138
138
|
function divide(a: number, b: number): DivisionError | number { ... }
|
|
139
139
|
|
|
140
140
|
// Compose with nested calls
|
|
141
|
-
const result = andThen(
|
|
142
|
-
andThen(parseNumber(input), validatePositive),
|
|
141
|
+
const result = errore.andThen(
|
|
142
|
+
errore.andThen(parseNumber(input), validatePositive),
|
|
143
143
|
n => divide(100, n)
|
|
144
144
|
)
|
|
145
145
|
|
|
146
146
|
// Or step by step (often clearer)
|
|
147
147
|
function calculate(input: string): ValidationError | DivisionError | number {
|
|
148
148
|
const parsed = parseNumber(input)
|
|
149
|
-
if (isError(parsed)) return parsed
|
|
149
|
+
if (errore.isError(parsed)) return parsed
|
|
150
150
|
|
|
151
151
|
const validated = validatePositive(parsed)
|
|
152
|
-
if (isError(validated)) return validated
|
|
152
|
+
if (errore.isError(validated)) return validated
|
|
153
153
|
|
|
154
154
|
return divide(100, validated)
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// Transform errors at the end
|
|
158
|
-
const appResult = mapError(
|
|
158
|
+
const appResult = errore.mapError(
|
|
159
159
|
calculate(userInput),
|
|
160
160
|
e => new AppError({ source: e._tag, message: e.message })
|
|
161
161
|
)
|
|
@@ -164,23 +164,25 @@ const appResult = mapError(
|
|
|
164
164
|
Real-world async composition:
|
|
165
165
|
|
|
166
166
|
```ts
|
|
167
|
+
import * as errore from 'errore'
|
|
168
|
+
|
|
167
169
|
async function processOrder(orderId: string): Promise<OrderError | Receipt> {
|
|
168
170
|
const order = await fetchOrder(orderId)
|
|
169
|
-
if (isError(order)) return order
|
|
171
|
+
if (errore.isError(order)) return order
|
|
170
172
|
|
|
171
173
|
const validated = validateOrder(order)
|
|
172
|
-
if (isError(validated)) return validated
|
|
174
|
+
if (errore.isError(validated)) return validated
|
|
173
175
|
|
|
174
176
|
const payment = await processPayment(validated)
|
|
175
|
-
if (isError(payment)) return payment
|
|
177
|
+
if (errore.isError(payment)) return payment
|
|
176
178
|
|
|
177
179
|
return generateReceipt(payment)
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
// Caller gets union of all possible errors
|
|
181
183
|
const receipt = await processOrder('123')
|
|
182
|
-
if (isError(receipt)) {
|
|
183
|
-
const message = matchError(receipt, {
|
|
184
|
+
if (errore.isError(receipt)) {
|
|
185
|
+
const message = errore.matchError(receipt, {
|
|
184
186
|
NotFoundError: e => `Order ${e.id} not found`,
|
|
185
187
|
ValidationError: e => `Invalid: ${e.field}`,
|
|
186
188
|
PaymentError: e => `Payment failed: ${e.reason}`,
|
|
@@ -193,37 +195,37 @@ if (isError(receipt)) {
|
|
|
193
195
|
### Extraction
|
|
194
196
|
|
|
195
197
|
```ts
|
|
196
|
-
import
|
|
198
|
+
import * as errore from 'errore'
|
|
197
199
|
|
|
198
200
|
// Extract or throw
|
|
199
|
-
const user = unwrap(result)
|
|
200
|
-
const user = unwrap(result, 'Custom error message')
|
|
201
|
+
const user = errore.unwrap(result)
|
|
202
|
+
const user = errore.unwrap(result, 'Custom error message')
|
|
201
203
|
|
|
202
204
|
// Extract or fallback
|
|
203
|
-
const name = unwrapOr(result, 'Anonymous')
|
|
205
|
+
const name = errore.unwrapOr(result, 'Anonymous')
|
|
204
206
|
|
|
205
207
|
// Pattern match
|
|
206
|
-
const message = match(result, {
|
|
208
|
+
const message = errore.match(result, {
|
|
207
209
|
ok: user => `Hello, ${user.name}`,
|
|
208
210
|
err: error => `Failed: ${error.message}`
|
|
209
211
|
})
|
|
210
212
|
|
|
211
213
|
// Split array into [successes, errors]
|
|
212
|
-
const [users, errors] = partition(results)
|
|
214
|
+
const [users, errors] = errore.partition(results)
|
|
213
215
|
```
|
|
214
216
|
|
|
215
217
|
### Tagged Errors
|
|
216
218
|
|
|
217
219
|
```ts
|
|
218
|
-
import
|
|
220
|
+
import * as errore from 'errore'
|
|
219
221
|
|
|
220
222
|
// Define errors with _tag discriminant
|
|
221
|
-
class ValidationError extends TaggedError('ValidationError')<{
|
|
223
|
+
class ValidationError extends errore.TaggedError('ValidationError')<{
|
|
222
224
|
field: string
|
|
223
225
|
message: string
|
|
224
226
|
}>() {}
|
|
225
227
|
|
|
226
|
-
class NetworkError extends TaggedError('NetworkError')<{
|
|
228
|
+
class NetworkError extends errore.TaggedError('NetworkError')<{
|
|
227
229
|
url: string
|
|
228
230
|
message: string
|
|
229
231
|
}>() {}
|
|
@@ -231,7 +233,7 @@ class NetworkError extends TaggedError('NetworkError')<{
|
|
|
231
233
|
type AppError = ValidationError | NetworkError
|
|
232
234
|
|
|
233
235
|
// Exhaustive matching (TypeScript ensures all cases handled)
|
|
234
|
-
const message = matchError(error, {
|
|
236
|
+
const message = errore.matchError(error, {
|
|
235
237
|
ValidationError: e => `Invalid ${e.field}`,
|
|
236
238
|
NetworkError: e => `Failed to fetch ${e.url}`
|
|
237
239
|
})
|
|
@@ -240,20 +242,55 @@ console.log(message)
|
|
|
240
242
|
// Handle plain Error with _ (underscore) handler
|
|
241
243
|
function riskyOp(): ValidationError | Error { ... }
|
|
242
244
|
const err = riskyOp()
|
|
243
|
-
const msg = matchError(err, {
|
|
245
|
+
const msg = errore.matchError(err, {
|
|
244
246
|
ValidationError: e => `Invalid ${e.field}`,
|
|
245
247
|
_: e => `Plain error: ${e.message}` // catches non-tagged Error
|
|
246
248
|
})
|
|
247
249
|
|
|
248
250
|
// Partial matching with fallback
|
|
249
|
-
const fallbackMsg = matchErrorPartial(error, {
|
|
251
|
+
const fallbackMsg = errore.matchErrorPartial(error, {
|
|
250
252
|
ValidationError: e => `Invalid ${e.field}`
|
|
251
253
|
}, e => `Unknown error: ${e.message}`)
|
|
252
254
|
console.log(fallbackMsg)
|
|
253
255
|
|
|
254
256
|
// Type guards
|
|
255
257
|
ValidationError.is(value) // specific class
|
|
256
|
-
TaggedError.is(value) // any tagged error
|
|
258
|
+
errore.TaggedError.is(value) // any tagged error
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Custom Base Class
|
|
262
|
+
|
|
263
|
+
Extend from your own base class to share functionality across all errors:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import * as errore from 'errore'
|
|
267
|
+
|
|
268
|
+
// Custom base with shared functionality
|
|
269
|
+
class AppError extends Error {
|
|
270
|
+
statusCode: number = 500
|
|
271
|
+
|
|
272
|
+
report() {
|
|
273
|
+
sentry.captureException(this)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Pass base class as second argument
|
|
278
|
+
class NotFoundError extends errore.TaggedError('NotFoundError', AppError)<{
|
|
279
|
+
id: string
|
|
280
|
+
message: string
|
|
281
|
+
}>() {
|
|
282
|
+
statusCode = 404
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
class ServerError extends errore.TaggedError('ServerError', AppError)<{
|
|
286
|
+
message: string
|
|
287
|
+
}>() {}
|
|
288
|
+
|
|
289
|
+
const err = new NotFoundError({ id: '123', message: 'User not found' })
|
|
290
|
+
err.statusCode // 404
|
|
291
|
+
err.report() // calls sentry
|
|
292
|
+
err._tag // 'NotFoundError'
|
|
293
|
+
err instanceof AppError // true
|
|
257
294
|
```
|
|
258
295
|
|
|
259
296
|
## How Type Safety Works
|
|
@@ -283,6 +320,8 @@ One of errore's best features: you can naturally combine error handling with opt
|
|
|
283
320
|
In Rust, you'd need `Result<Option<T>, E>` or `Option<Result<T, E>>` and worry about the order. Here it's just a union:
|
|
284
321
|
|
|
285
322
|
```ts
|
|
323
|
+
import * as errore from 'errore'
|
|
324
|
+
|
|
286
325
|
// Result + Option in one natural type
|
|
287
326
|
function findUser(id: string): NotFoundError | User | null {
|
|
288
327
|
if (id === 'bad') return new NotFoundError({ id })
|
|
@@ -293,7 +332,7 @@ function findUser(id: string): NotFoundError | User | null {
|
|
|
293
332
|
const user = findUser('123')
|
|
294
333
|
|
|
295
334
|
// Handle error first
|
|
296
|
-
if (isError(user)) {
|
|
335
|
+
if (errore.isError(user)) {
|
|
297
336
|
return user.message // TypeScript: user is NotFoundError
|
|
298
337
|
}
|
|
299
338
|
|
|
@@ -312,6 +351,8 @@ console.log(user.name)
|
|
|
312
351
|
### Works with `undefined` too
|
|
313
352
|
|
|
314
353
|
```ts
|
|
354
|
+
import * as errore from 'errore'
|
|
355
|
+
|
|
315
356
|
function lookup(key: string): NetworkError | string | undefined {
|
|
316
357
|
if (key === 'fail') return new NetworkError({ url: '/api', message: 'Failed' })
|
|
317
358
|
if (key === 'missing') return undefined
|
|
@@ -320,7 +361,7 @@ function lookup(key: string): NetworkError | string | undefined {
|
|
|
320
361
|
|
|
321
362
|
const value = lookup('key')
|
|
322
363
|
|
|
323
|
-
if (isError(value)) return value
|
|
364
|
+
if (errore.isError(value)) return value
|
|
324
365
|
|
|
325
366
|
// ?? works naturally with undefined
|
|
326
367
|
const result = value ?? 'default'
|
|
@@ -331,6 +372,8 @@ const result = value ?? 'default'
|
|
|
331
372
|
Even this works with full type inference:
|
|
332
373
|
|
|
333
374
|
```ts
|
|
375
|
+
import * as errore from 'errore'
|
|
376
|
+
|
|
334
377
|
function query(sql: string): ValidationError | { rows: string[] } | null | undefined {
|
|
335
378
|
if (sql === 'invalid') return new ValidationError({ field: 'sql', message: 'Bad' })
|
|
336
379
|
if (sql === 'empty') return null // explicitly no data
|
|
@@ -340,7 +383,7 @@ function query(sql: string): ValidationError | { rows: string[] } | null | undef
|
|
|
340
383
|
|
|
341
384
|
const result = query('SELECT *')
|
|
342
385
|
|
|
343
|
-
if (isError(result)) {
|
|
386
|
+
if (errore.isError(result)) {
|
|
344
387
|
return result.field // TypeScript: ValidationError
|
|
345
388
|
}
|
|
346
389
|
|
|
@@ -377,6 +420,10 @@ With errore:
|
|
|
377
420
|
| `Result<User, Error>` | `Error \| User` |
|
|
378
421
|
| `Result<Option<T>, E>` | `Error \| T \| null` |
|
|
379
422
|
|
|
423
|
+
## Import Style
|
|
424
|
+
|
|
425
|
+
> **Note:** Always use `import * as errore from 'errore'` instead of named imports. This makes code easier to move between files, and more readable for people unfamiliar with errore since every function call is clearly namespaced (e.g. `errore.isError()` instead of just `isError()`).
|
|
426
|
+
|
|
380
427
|
## License
|
|
381
428
|
|
|
382
429
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -25,20 +25,24 @@ type EnsureNotError<T> = T extends Error ? 'Error: Value type T cannot extend Er
|
|
|
25
25
|
type AnyTaggedError = Error & {
|
|
26
26
|
readonly _tag: string;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Any class that extends Error
|
|
30
|
+
*/
|
|
31
|
+
type ErrorClass = new (...args: any[]) => Error;
|
|
28
32
|
/**
|
|
29
33
|
* Instance type produced by TaggedError factory
|
|
30
34
|
*/
|
|
31
|
-
type TaggedErrorInstance<Tag extends string, Props
|
|
35
|
+
type TaggedErrorInstance<Tag extends string, Props, Base extends Error = Error> = Base & {
|
|
32
36
|
readonly _tag: Tag;
|
|
33
37
|
toJSON(): object;
|
|
34
38
|
} & Readonly<Props>;
|
|
35
39
|
/**
|
|
36
40
|
* Class type produced by TaggedError factory
|
|
37
41
|
*/
|
|
38
|
-
type TaggedErrorClass<Tag extends string, Props> = {
|
|
39
|
-
new (...args: keyof Props extends never ? [args?: {}] : [args: Props]): TaggedErrorInstance<Tag, Props>;
|
|
42
|
+
type TaggedErrorClass<Tag extends string, Props, Base extends Error = Error> = {
|
|
43
|
+
new (...args: keyof Props extends never ? [args?: {}] : [args: Props]): TaggedErrorInstance<Tag, Props, Base>;
|
|
40
44
|
/** Type guard for this error class */
|
|
41
|
-
is(value: unknown): value is TaggedErrorInstance<Tag, Props>;
|
|
45
|
+
is(value: unknown): value is TaggedErrorInstance<Tag, Props, Base>;
|
|
42
46
|
};
|
|
43
47
|
/**
|
|
44
48
|
* Factory for tagged error classes with discriminated _tag property.
|
|
@@ -57,9 +61,27 @@ type TaggedErrorClass<Tag extends string, Props> = {
|
|
|
57
61
|
* // Type guard
|
|
58
62
|
* NotFoundError.is(err) // true
|
|
59
63
|
* TaggedError.is(err) // true (any tagged error)
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // With custom base class
|
|
67
|
+
* class AppError extends Error {
|
|
68
|
+
* statusCode: number = 500
|
|
69
|
+
* report() { console.log(this.message) }
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* class NotFoundError extends TaggedError("NotFoundError", AppError)<{
|
|
73
|
+
* id: string;
|
|
74
|
+
* message: string;
|
|
75
|
+
* }>() {
|
|
76
|
+
* statusCode = 404
|
|
77
|
+
* }
|
|
78
|
+
*
|
|
79
|
+
* const err = new NotFoundError({ id: "123", message: "Not found" });
|
|
80
|
+
* err.statusCode // 404
|
|
81
|
+
* err.report() // works
|
|
60
82
|
*/
|
|
61
83
|
declare const TaggedError: {
|
|
62
|
-
<Tag extends string>(tag: Tag): <Props extends Record<string, unknown> = {}>() => TaggedErrorClass<Tag, Props
|
|
84
|
+
<Tag extends string, BaseClass extends ErrorClass = typeof Error>(tag: Tag, BaseClass?: BaseClass): <Props extends Record<string, unknown> = {}>() => TaggedErrorClass<Tag, Props, InstanceType<BaseClass>>;
|
|
63
85
|
/** Type guard for any TaggedError instance */
|
|
64
86
|
is(value: unknown): value is AnyTaggedError;
|
|
65
87
|
};
|
|
@@ -111,7 +133,7 @@ declare function matchErrorPartial<E extends Error, R>(err: E, handlers: Partial
|
|
|
111
133
|
declare const UnhandledError_base: TaggedErrorClass<"UnhandledError", {
|
|
112
134
|
message: string;
|
|
113
135
|
cause: unknown;
|
|
114
|
-
}>;
|
|
136
|
+
}, Error>;
|
|
115
137
|
/**
|
|
116
138
|
* Default error type when catching unknown exceptions.
|
|
117
139
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -25,20 +25,24 @@ type EnsureNotError<T> = T extends Error ? 'Error: Value type T cannot extend Er
|
|
|
25
25
|
type AnyTaggedError = Error & {
|
|
26
26
|
readonly _tag: string;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Any class that extends Error
|
|
30
|
+
*/
|
|
31
|
+
type ErrorClass = new (...args: any[]) => Error;
|
|
28
32
|
/**
|
|
29
33
|
* Instance type produced by TaggedError factory
|
|
30
34
|
*/
|
|
31
|
-
type TaggedErrorInstance<Tag extends string, Props
|
|
35
|
+
type TaggedErrorInstance<Tag extends string, Props, Base extends Error = Error> = Base & {
|
|
32
36
|
readonly _tag: Tag;
|
|
33
37
|
toJSON(): object;
|
|
34
38
|
} & Readonly<Props>;
|
|
35
39
|
/**
|
|
36
40
|
* Class type produced by TaggedError factory
|
|
37
41
|
*/
|
|
38
|
-
type TaggedErrorClass<Tag extends string, Props> = {
|
|
39
|
-
new (...args: keyof Props extends never ? [args?: {}] : [args: Props]): TaggedErrorInstance<Tag, Props>;
|
|
42
|
+
type TaggedErrorClass<Tag extends string, Props, Base extends Error = Error> = {
|
|
43
|
+
new (...args: keyof Props extends never ? [args?: {}] : [args: Props]): TaggedErrorInstance<Tag, Props, Base>;
|
|
40
44
|
/** Type guard for this error class */
|
|
41
|
-
is(value: unknown): value is TaggedErrorInstance<Tag, Props>;
|
|
45
|
+
is(value: unknown): value is TaggedErrorInstance<Tag, Props, Base>;
|
|
42
46
|
};
|
|
43
47
|
/**
|
|
44
48
|
* Factory for tagged error classes with discriminated _tag property.
|
|
@@ -57,9 +61,27 @@ type TaggedErrorClass<Tag extends string, Props> = {
|
|
|
57
61
|
* // Type guard
|
|
58
62
|
* NotFoundError.is(err) // true
|
|
59
63
|
* TaggedError.is(err) // true (any tagged error)
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // With custom base class
|
|
67
|
+
* class AppError extends Error {
|
|
68
|
+
* statusCode: number = 500
|
|
69
|
+
* report() { console.log(this.message) }
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* class NotFoundError extends TaggedError("NotFoundError", AppError)<{
|
|
73
|
+
* id: string;
|
|
74
|
+
* message: string;
|
|
75
|
+
* }>() {
|
|
76
|
+
* statusCode = 404
|
|
77
|
+
* }
|
|
78
|
+
*
|
|
79
|
+
* const err = new NotFoundError({ id: "123", message: "Not found" });
|
|
80
|
+
* err.statusCode // 404
|
|
81
|
+
* err.report() // works
|
|
60
82
|
*/
|
|
61
83
|
declare const TaggedError: {
|
|
62
|
-
<Tag extends string>(tag: Tag): <Props extends Record<string, unknown> = {}>() => TaggedErrorClass<Tag, Props
|
|
84
|
+
<Tag extends string, BaseClass extends ErrorClass = typeof Error>(tag: Tag, BaseClass?: BaseClass): <Props extends Record<string, unknown> = {}>() => TaggedErrorClass<Tag, Props, InstanceType<BaseClass>>;
|
|
63
85
|
/** Type guard for any TaggedError instance */
|
|
64
86
|
is(value: unknown): value is AnyTaggedError;
|
|
65
87
|
};
|
|
@@ -111,7 +133,7 @@ declare function matchErrorPartial<E extends Error, R>(err: E, handlers: Partial
|
|
|
111
133
|
declare const UnhandledError_base: TaggedErrorClass<"UnhandledError", {
|
|
112
134
|
message: string;
|
|
113
135
|
cause: unknown;
|
|
114
|
-
}>;
|
|
136
|
+
}, Error>;
|
|
115
137
|
/**
|
|
116
138
|
* Default error type when catching unknown exceptions.
|
|
117
139
|
*/
|
package/dist/index.js
CHANGED
|
@@ -54,12 +54,13 @@ var isAnyTaggedError = (value) => {
|
|
|
54
54
|
return value instanceof Error && "_tag" in value && typeof value._tag === "string";
|
|
55
55
|
};
|
|
56
56
|
var TaggedError = Object.assign(
|
|
57
|
-
(tag) => () => {
|
|
58
|
-
|
|
57
|
+
(tag, BaseClass) => () => {
|
|
58
|
+
const ActualBase = BaseClass ?? Error;
|
|
59
|
+
class Tagged extends ActualBase {
|
|
59
60
|
_tag = tag;
|
|
60
61
|
/** Type guard for this error class */
|
|
61
62
|
static is(value) {
|
|
62
|
-
return value instanceof
|
|
63
|
+
return value instanceof Tagged;
|
|
63
64
|
}
|
|
64
65
|
constructor(args) {
|
|
65
66
|
const message = args && "message" in args && typeof args.message === "string" ? args.message : void 0;
|
|
@@ -87,7 +88,7 @@ Caused by: ${indented}`;
|
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
return
|
|
91
|
+
return Tagged;
|
|
91
92
|
},
|
|
92
93
|
{ is: isAnyTaggedError }
|
|
93
94
|
);
|
package/dist/index.mjs
CHANGED
|
@@ -9,12 +9,13 @@ var isAnyTaggedError = (value) => {
|
|
|
9
9
|
return value instanceof Error && "_tag" in value && typeof value._tag === "string";
|
|
10
10
|
};
|
|
11
11
|
var TaggedError = Object.assign(
|
|
12
|
-
(tag) => () => {
|
|
13
|
-
|
|
12
|
+
(tag, BaseClass) => () => {
|
|
13
|
+
const ActualBase = BaseClass ?? Error;
|
|
14
|
+
class Tagged extends ActualBase {
|
|
14
15
|
_tag = tag;
|
|
15
16
|
/** Type guard for this error class */
|
|
16
17
|
static is(value) {
|
|
17
|
-
return value instanceof
|
|
18
|
+
return value instanceof Tagged;
|
|
18
19
|
}
|
|
19
20
|
constructor(args) {
|
|
20
21
|
const message = args && "message" in args && typeof args.message === "string" ? args.message : void 0;
|
|
@@ -42,7 +43,7 @@ Caused by: ${indented}`;
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
return
|
|
46
|
+
return Tagged;
|
|
46
47
|
},
|
|
47
48
|
{ is: isAnyTaggedError }
|
|
48
49
|
);
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "errore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Type-safe errors as values for TypeScript. Like Go, but with full type inference.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/remorses/errore.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/remorses/errore#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/remorses/errore/issues"
|
|
12
|
+
},
|
|
5
13
|
"main": "dist/index.js",
|
|
6
14
|
"module": "dist/index.mjs",
|
|
7
15
|
"types": "dist/index.d.ts",
|