errore 0.5.2 → 0.7.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 +165 -46
- package/dist/{index.mjs → index.cjs} +53 -6
- package/dist/{index.d.mts → index.d.cts} +28 -6
- package/dist/index.d.ts +28 -6
- package/dist/index.js +7 -52
- package/package.json +10 -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
|
})
|
|
@@ -67,16 +67,88 @@ if (isError(user)) {
|
|
|
67
67
|
console.log(user.name)
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
## Example: API Error Handling
|
|
71
|
+
|
|
72
|
+
A complete example with custom base class, HTTP status codes, and error reporting:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import * as errore from 'errore'
|
|
76
|
+
|
|
77
|
+
// Base class with shared functionality
|
|
78
|
+
class AppError extends Error {
|
|
79
|
+
statusCode: number = 500
|
|
80
|
+
|
|
81
|
+
toResponse() {
|
|
82
|
+
return { error: this.message, code: this.statusCode }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Specific errors with status codes
|
|
87
|
+
class NotFoundError extends errore.TaggedError('NotFoundError', AppError)<{
|
|
88
|
+
resource: string
|
|
89
|
+
message: string
|
|
90
|
+
}>() {
|
|
91
|
+
statusCode = 404
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class ValidationError extends errore.TaggedError('ValidationError', AppError)<{
|
|
95
|
+
field: string
|
|
96
|
+
message: string
|
|
97
|
+
}>() {
|
|
98
|
+
statusCode = 400
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class UnauthorizedError extends errore.TaggedError('UnauthorizedError', AppError)<{
|
|
102
|
+
message: string
|
|
103
|
+
}>() {
|
|
104
|
+
statusCode = 401
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Service function
|
|
108
|
+
async function updateUser(
|
|
109
|
+
userId: string,
|
|
110
|
+
data: { email?: string }
|
|
111
|
+
): Promise<NotFoundError | ValidationError | UnauthorizedError | User> {
|
|
112
|
+
const session = await getSession()
|
|
113
|
+
if (!session) {
|
|
114
|
+
return new UnauthorizedError({ message: 'Not logged in' })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const user = await db.users.find(userId)
|
|
118
|
+
if (!user) {
|
|
119
|
+
return new NotFoundError({ resource: 'user', message: `User ${userId} not found` })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (data.email && !isValidEmail(data.email)) {
|
|
123
|
+
return new ValidationError({ field: 'email', message: 'Invalid email format' })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return db.users.update(userId, data)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// API handler
|
|
130
|
+
app.post('/users/:id', async (req, res) => {
|
|
131
|
+
const result = await updateUser(req.params.id, req.body)
|
|
132
|
+
|
|
133
|
+
if (errore.isError(result)) {
|
|
134
|
+
// All errors have toResponse() from AppError base
|
|
135
|
+
return res.status(result.statusCode).json(result.toResponse())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return res.json(result)
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
70
142
|
## API
|
|
71
143
|
|
|
72
144
|
### Type Guards
|
|
73
145
|
|
|
74
146
|
```ts
|
|
75
|
-
import
|
|
147
|
+
import * as errore from 'errore'
|
|
76
148
|
|
|
77
149
|
const result: NetworkError | User = await fetchUser(id)
|
|
78
150
|
|
|
79
|
-
if (isError(result)) {
|
|
151
|
+
if (errore.isError(result)) {
|
|
80
152
|
// result is NetworkError
|
|
81
153
|
return result
|
|
82
154
|
}
|
|
@@ -86,22 +158,22 @@ if (isError(result)) {
|
|
|
86
158
|
### Try Functions
|
|
87
159
|
|
|
88
160
|
```ts
|
|
89
|
-
import
|
|
161
|
+
import * as errore from 'errore'
|
|
90
162
|
|
|
91
163
|
// Sync - wraps exceptions in UnhandledError
|
|
92
|
-
const parsed = tryFn(() => JSON.parse(input))
|
|
164
|
+
const parsed = errore.tryFn(() => JSON.parse(input))
|
|
93
165
|
|
|
94
166
|
// Sync - with custom error type
|
|
95
|
-
const parsed = tryFn({
|
|
167
|
+
const parsed = errore.tryFn({
|
|
96
168
|
try: () => JSON.parse(input),
|
|
97
169
|
catch: e => new ParseError({ cause: e })
|
|
98
170
|
})
|
|
99
171
|
|
|
100
172
|
// Async
|
|
101
|
-
const response = await tryAsync(() => fetch(url))
|
|
173
|
+
const response = await errore.tryAsync(() => fetch(url))
|
|
102
174
|
|
|
103
175
|
// Async - with custom error
|
|
104
|
-
const response = await tryAsync({
|
|
176
|
+
const response = await errore.tryAsync({
|
|
105
177
|
try: () => fetch(url),
|
|
106
178
|
catch: e => new NetworkError({ cause: e })
|
|
107
179
|
})
|
|
@@ -110,19 +182,19 @@ const response = await tryAsync({
|
|
|
110
182
|
### Transformations
|
|
111
183
|
|
|
112
184
|
```ts
|
|
113
|
-
import
|
|
185
|
+
import * as errore from 'errore'
|
|
114
186
|
|
|
115
187
|
// Transform value (if not error)
|
|
116
|
-
const name = map(user, u => u.name)
|
|
188
|
+
const name = errore.map(user, u => u.name)
|
|
117
189
|
|
|
118
190
|
// Transform error
|
|
119
|
-
const appError = mapError(dbError, e => new AppError({ cause: e }))
|
|
191
|
+
const appError = errore.mapError(dbError, e => new AppError({ cause: e }))
|
|
120
192
|
|
|
121
193
|
// Chain operations
|
|
122
|
-
const posts = andThen(user, u => fetchPosts(u.id))
|
|
194
|
+
const posts = errore.andThen(user, u => fetchPosts(u.id))
|
|
123
195
|
|
|
124
196
|
// Side effects
|
|
125
|
-
const logged = tap(user, u => console.log('Got user:', u.name))
|
|
197
|
+
const logged = errore.tap(user, u => console.log('Got user:', u.name))
|
|
126
198
|
```
|
|
127
199
|
|
|
128
200
|
### Composing Operations
|
|
@@ -130,7 +202,7 @@ const logged = tap(user, u => console.log('Got user:', u.name))
|
|
|
130
202
|
Chain multiple operations together:
|
|
131
203
|
|
|
132
204
|
```ts
|
|
133
|
-
import
|
|
205
|
+
import * as errore from 'errore'
|
|
134
206
|
|
|
135
207
|
// Define operations that can fail
|
|
136
208
|
function parseNumber(s: string): ValidationError | number { ... }
|
|
@@ -138,24 +210,24 @@ function validatePositive(n: number): ValidationError | number { ... }
|
|
|
138
210
|
function divide(a: number, b: number): DivisionError | number { ... }
|
|
139
211
|
|
|
140
212
|
// Compose with nested calls
|
|
141
|
-
const result = andThen(
|
|
142
|
-
andThen(parseNumber(input), validatePositive),
|
|
213
|
+
const result = errore.andThen(
|
|
214
|
+
errore.andThen(parseNumber(input), validatePositive),
|
|
143
215
|
n => divide(100, n)
|
|
144
216
|
)
|
|
145
217
|
|
|
146
218
|
// Or step by step (often clearer)
|
|
147
219
|
function calculate(input: string): ValidationError | DivisionError | number {
|
|
148
220
|
const parsed = parseNumber(input)
|
|
149
|
-
if (isError(parsed)) return parsed
|
|
221
|
+
if (errore.isError(parsed)) return parsed
|
|
150
222
|
|
|
151
223
|
const validated = validatePositive(parsed)
|
|
152
|
-
if (isError(validated)) return validated
|
|
224
|
+
if (errore.isError(validated)) return validated
|
|
153
225
|
|
|
154
226
|
return divide(100, validated)
|
|
155
227
|
}
|
|
156
228
|
|
|
157
229
|
// Transform errors at the end
|
|
158
|
-
const appResult = mapError(
|
|
230
|
+
const appResult = errore.mapError(
|
|
159
231
|
calculate(userInput),
|
|
160
232
|
e => new AppError({ source: e._tag, message: e.message })
|
|
161
233
|
)
|
|
@@ -164,23 +236,25 @@ const appResult = mapError(
|
|
|
164
236
|
Real-world async composition:
|
|
165
237
|
|
|
166
238
|
```ts
|
|
239
|
+
import * as errore from 'errore'
|
|
240
|
+
|
|
167
241
|
async function processOrder(orderId: string): Promise<OrderError | Receipt> {
|
|
168
242
|
const order = await fetchOrder(orderId)
|
|
169
|
-
if (isError(order)) return order
|
|
243
|
+
if (errore.isError(order)) return order
|
|
170
244
|
|
|
171
245
|
const validated = validateOrder(order)
|
|
172
|
-
if (isError(validated)) return validated
|
|
246
|
+
if (errore.isError(validated)) return validated
|
|
173
247
|
|
|
174
248
|
const payment = await processPayment(validated)
|
|
175
|
-
if (isError(payment)) return payment
|
|
249
|
+
if (errore.isError(payment)) return payment
|
|
176
250
|
|
|
177
251
|
return generateReceipt(payment)
|
|
178
252
|
}
|
|
179
253
|
|
|
180
254
|
// Caller gets union of all possible errors
|
|
181
255
|
const receipt = await processOrder('123')
|
|
182
|
-
if (isError(receipt)) {
|
|
183
|
-
const message = matchError(receipt, {
|
|
256
|
+
if (errore.isError(receipt)) {
|
|
257
|
+
const message = errore.matchError(receipt, {
|
|
184
258
|
NotFoundError: e => `Order ${e.id} not found`,
|
|
185
259
|
ValidationError: e => `Invalid: ${e.field}`,
|
|
186
260
|
PaymentError: e => `Payment failed: ${e.reason}`,
|
|
@@ -193,37 +267,37 @@ if (isError(receipt)) {
|
|
|
193
267
|
### Extraction
|
|
194
268
|
|
|
195
269
|
```ts
|
|
196
|
-
import
|
|
270
|
+
import * as errore from 'errore'
|
|
197
271
|
|
|
198
272
|
// Extract or throw
|
|
199
|
-
const user = unwrap(result)
|
|
200
|
-
const user = unwrap(result, 'Custom error message')
|
|
273
|
+
const user = errore.unwrap(result)
|
|
274
|
+
const user = errore.unwrap(result, 'Custom error message')
|
|
201
275
|
|
|
202
276
|
// Extract or fallback
|
|
203
|
-
const name = unwrapOr(result, 'Anonymous')
|
|
277
|
+
const name = errore.unwrapOr(result, 'Anonymous')
|
|
204
278
|
|
|
205
279
|
// Pattern match
|
|
206
|
-
const message = match(result, {
|
|
280
|
+
const message = errore.match(result, {
|
|
207
281
|
ok: user => `Hello, ${user.name}`,
|
|
208
282
|
err: error => `Failed: ${error.message}`
|
|
209
283
|
})
|
|
210
284
|
|
|
211
285
|
// Split array into [successes, errors]
|
|
212
|
-
const [users, errors] = partition(results)
|
|
286
|
+
const [users, errors] = errore.partition(results)
|
|
213
287
|
```
|
|
214
288
|
|
|
215
289
|
### Tagged Errors
|
|
216
290
|
|
|
217
291
|
```ts
|
|
218
|
-
import
|
|
292
|
+
import * as errore from 'errore'
|
|
219
293
|
|
|
220
294
|
// Define errors with _tag discriminant
|
|
221
|
-
class ValidationError extends TaggedError('ValidationError')<{
|
|
295
|
+
class ValidationError extends errore.TaggedError('ValidationError')<{
|
|
222
296
|
field: string
|
|
223
297
|
message: string
|
|
224
298
|
}>() {}
|
|
225
299
|
|
|
226
|
-
class NetworkError extends TaggedError('NetworkError')<{
|
|
300
|
+
class NetworkError extends errore.TaggedError('NetworkError')<{
|
|
227
301
|
url: string
|
|
228
302
|
message: string
|
|
229
303
|
}>() {}
|
|
@@ -231,7 +305,7 @@ class NetworkError extends TaggedError('NetworkError')<{
|
|
|
231
305
|
type AppError = ValidationError | NetworkError
|
|
232
306
|
|
|
233
307
|
// Exhaustive matching (TypeScript ensures all cases handled)
|
|
234
|
-
const message = matchError(error, {
|
|
308
|
+
const message = errore.matchError(error, {
|
|
235
309
|
ValidationError: e => `Invalid ${e.field}`,
|
|
236
310
|
NetworkError: e => `Failed to fetch ${e.url}`
|
|
237
311
|
})
|
|
@@ -240,20 +314,55 @@ console.log(message)
|
|
|
240
314
|
// Handle plain Error with _ (underscore) handler
|
|
241
315
|
function riskyOp(): ValidationError | Error { ... }
|
|
242
316
|
const err = riskyOp()
|
|
243
|
-
const msg = matchError(err, {
|
|
317
|
+
const msg = errore.matchError(err, {
|
|
244
318
|
ValidationError: e => `Invalid ${e.field}`,
|
|
245
319
|
_: e => `Plain error: ${e.message}` // catches non-tagged Error
|
|
246
320
|
})
|
|
247
321
|
|
|
248
322
|
// Partial matching with fallback
|
|
249
|
-
const fallbackMsg = matchErrorPartial(error, {
|
|
323
|
+
const fallbackMsg = errore.matchErrorPartial(error, {
|
|
250
324
|
ValidationError: e => `Invalid ${e.field}`
|
|
251
325
|
}, e => `Unknown error: ${e.message}`)
|
|
252
326
|
console.log(fallbackMsg)
|
|
253
327
|
|
|
254
328
|
// Type guards
|
|
255
329
|
ValidationError.is(value) // specific class
|
|
256
|
-
TaggedError.is(value) // any tagged error
|
|
330
|
+
errore.TaggedError.is(value) // any tagged error
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Custom Base Class
|
|
334
|
+
|
|
335
|
+
Extend from your own base class to share functionality across all errors:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import * as errore from 'errore'
|
|
339
|
+
|
|
340
|
+
// Custom base with shared functionality
|
|
341
|
+
class AppError extends Error {
|
|
342
|
+
statusCode: number = 500
|
|
343
|
+
|
|
344
|
+
report() {
|
|
345
|
+
sentry.captureException(this)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Pass base class as second argument
|
|
350
|
+
class NotFoundError extends errore.TaggedError('NotFoundError', AppError)<{
|
|
351
|
+
id: string
|
|
352
|
+
message: string
|
|
353
|
+
}>() {
|
|
354
|
+
statusCode = 404
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
class ServerError extends errore.TaggedError('ServerError', AppError)<{
|
|
358
|
+
message: string
|
|
359
|
+
}>() {}
|
|
360
|
+
|
|
361
|
+
const err = new NotFoundError({ id: '123', message: 'User not found' })
|
|
362
|
+
err.statusCode // 404
|
|
363
|
+
err.report() // calls sentry
|
|
364
|
+
err._tag // 'NotFoundError'
|
|
365
|
+
err instanceof AppError // true
|
|
257
366
|
```
|
|
258
367
|
|
|
259
368
|
## How Type Safety Works
|
|
@@ -283,6 +392,8 @@ One of errore's best features: you can naturally combine error handling with opt
|
|
|
283
392
|
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
393
|
|
|
285
394
|
```ts
|
|
395
|
+
import * as errore from 'errore'
|
|
396
|
+
|
|
286
397
|
// Result + Option in one natural type
|
|
287
398
|
function findUser(id: string): NotFoundError | User | null {
|
|
288
399
|
if (id === 'bad') return new NotFoundError({ id })
|
|
@@ -293,7 +404,7 @@ function findUser(id: string): NotFoundError | User | null {
|
|
|
293
404
|
const user = findUser('123')
|
|
294
405
|
|
|
295
406
|
// Handle error first
|
|
296
|
-
if (isError(user)) {
|
|
407
|
+
if (errore.isError(user)) {
|
|
297
408
|
return user.message // TypeScript: user is NotFoundError
|
|
298
409
|
}
|
|
299
410
|
|
|
@@ -312,6 +423,8 @@ console.log(user.name)
|
|
|
312
423
|
### Works with `undefined` too
|
|
313
424
|
|
|
314
425
|
```ts
|
|
426
|
+
import * as errore from 'errore'
|
|
427
|
+
|
|
315
428
|
function lookup(key: string): NetworkError | string | undefined {
|
|
316
429
|
if (key === 'fail') return new NetworkError({ url: '/api', message: 'Failed' })
|
|
317
430
|
if (key === 'missing') return undefined
|
|
@@ -320,7 +433,7 @@ function lookup(key: string): NetworkError | string | undefined {
|
|
|
320
433
|
|
|
321
434
|
const value = lookup('key')
|
|
322
435
|
|
|
323
|
-
if (isError(value)) return value
|
|
436
|
+
if (errore.isError(value)) return value
|
|
324
437
|
|
|
325
438
|
// ?? works naturally with undefined
|
|
326
439
|
const result = value ?? 'default'
|
|
@@ -331,6 +444,8 @@ const result = value ?? 'default'
|
|
|
331
444
|
Even this works with full type inference:
|
|
332
445
|
|
|
333
446
|
```ts
|
|
447
|
+
import * as errore from 'errore'
|
|
448
|
+
|
|
334
449
|
function query(sql: string): ValidationError | { rows: string[] } | null | undefined {
|
|
335
450
|
if (sql === 'invalid') return new ValidationError({ field: 'sql', message: 'Bad' })
|
|
336
451
|
if (sql === 'empty') return null // explicitly no data
|
|
@@ -340,7 +455,7 @@ function query(sql: string): ValidationError | { rows: string[] } | null | undef
|
|
|
340
455
|
|
|
341
456
|
const result = query('SELECT *')
|
|
342
457
|
|
|
343
|
-
if (isError(result)) {
|
|
458
|
+
if (errore.isError(result)) {
|
|
344
459
|
return result.field // TypeScript: ValidationError
|
|
345
460
|
}
|
|
346
461
|
|
|
@@ -377,6 +492,10 @@ With errore:
|
|
|
377
492
|
| `Result<User, Error>` | `Error \| User` |
|
|
378
493
|
| `Result<Option<T>, E>` | `Error \| T \| null` |
|
|
379
494
|
|
|
495
|
+
## Import Style
|
|
496
|
+
|
|
497
|
+
> **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()`).
|
|
498
|
+
|
|
380
499
|
## License
|
|
381
500
|
|
|
382
501
|
MIT
|
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
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
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
1
46
|
// src/error.ts
|
|
2
47
|
var serializeCause = (cause) => {
|
|
3
48
|
if (cause instanceof Error) {
|
|
@@ -9,12 +54,13 @@ var isAnyTaggedError = (value) => {
|
|
|
9
54
|
return value instanceof Error && "_tag" in value && typeof value._tag === "string";
|
|
10
55
|
};
|
|
11
56
|
var TaggedError = Object.assign(
|
|
12
|
-
(tag) => () => {
|
|
13
|
-
|
|
57
|
+
(tag, BaseClass) => () => {
|
|
58
|
+
const ActualBase = BaseClass ?? Error;
|
|
59
|
+
class Tagged extends ActualBase {
|
|
14
60
|
_tag = tag;
|
|
15
61
|
/** Type guard for this error class */
|
|
16
62
|
static is(value) {
|
|
17
|
-
return value instanceof
|
|
63
|
+
return value instanceof Tagged;
|
|
18
64
|
}
|
|
19
65
|
constructor(args) {
|
|
20
66
|
const message = args && "message" in args && typeof args.message === "string" ? args.message : void 0;
|
|
@@ -42,7 +88,7 @@ Caused by: ${indented}`;
|
|
|
42
88
|
};
|
|
43
89
|
}
|
|
44
90
|
}
|
|
45
|
-
return
|
|
91
|
+
return Tagged;
|
|
46
92
|
},
|
|
47
93
|
{ is: isAnyTaggedError }
|
|
48
94
|
);
|
|
@@ -190,7 +236,8 @@ function partition(values) {
|
|
|
190
236
|
function flatten(value) {
|
|
191
237
|
return value;
|
|
192
238
|
}
|
|
193
|
-
export
|
|
239
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
240
|
+
0 && (module.exports = {
|
|
194
241
|
TaggedError,
|
|
195
242
|
UnhandledError,
|
|
196
243
|
andThen,
|
|
@@ -211,4 +258,4 @@ export {
|
|
|
211
258
|
tryFn,
|
|
212
259
|
unwrap,
|
|
213
260
|
unwrapOr
|
|
214
|
-
};
|
|
261
|
+
});
|
|
@@ -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
|
@@ -1,48 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
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
|
|
43
|
-
});
|
|
44
|
-
module.exports = __toCommonJS(index_exports);
|
|
45
|
-
|
|
46
1
|
// src/error.ts
|
|
47
2
|
var serializeCause = (cause) => {
|
|
48
3
|
if (cause instanceof Error) {
|
|
@@ -54,12 +9,13 @@ var isAnyTaggedError = (value) => {
|
|
|
54
9
|
return value instanceof Error && "_tag" in value && typeof value._tag === "string";
|
|
55
10
|
};
|
|
56
11
|
var TaggedError = Object.assign(
|
|
57
|
-
(tag) => () => {
|
|
58
|
-
|
|
12
|
+
(tag, BaseClass) => () => {
|
|
13
|
+
const ActualBase = BaseClass ?? Error;
|
|
14
|
+
class Tagged extends ActualBase {
|
|
59
15
|
_tag = tag;
|
|
60
16
|
/** Type guard for this error class */
|
|
61
17
|
static is(value) {
|
|
62
|
-
return value instanceof
|
|
18
|
+
return value instanceof Tagged;
|
|
63
19
|
}
|
|
64
20
|
constructor(args) {
|
|
65
21
|
const message = args && "message" in args && typeof args.message === "string" ? args.message : void 0;
|
|
@@ -87,7 +43,7 @@ Caused by: ${indented}`;
|
|
|
87
43
|
};
|
|
88
44
|
}
|
|
89
45
|
}
|
|
90
|
-
return
|
|
46
|
+
return Tagged;
|
|
91
47
|
},
|
|
92
48
|
{ is: isAnyTaggedError }
|
|
93
49
|
);
|
|
@@ -235,8 +191,7 @@ function partition(values) {
|
|
|
235
191
|
function flatten(value) {
|
|
236
192
|
return value;
|
|
237
193
|
}
|
|
238
|
-
|
|
239
|
-
0 && (module.exports = {
|
|
194
|
+
export {
|
|
240
195
|
TaggedError,
|
|
241
196
|
UnhandledError,
|
|
242
197
|
andThen,
|
|
@@ -257,4 +212,4 @@ function flatten(value) {
|
|
|
257
212
|
tryFn,
|
|
258
213
|
unwrap,
|
|
259
214
|
unwrapOr
|
|
260
|
-
}
|
|
215
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "errore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "Type-safe errors as values for TypeScript. Like Go, but with full type inference.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/remorses/errore.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/remorses/errore#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/remorses/errore/issues"
|
|
13
|
+
},
|
|
5
14
|
"main": "dist/index.js",
|
|
6
15
|
"module": "dist/index.mjs",
|
|
7
16
|
"types": "dist/index.d.ts",
|