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 CHANGED
@@ -22,10 +22,10 @@ npm install errore
22
22
  ## Quick Start
23
23
 
24
24
  ```ts
25
- import { tryAsync, isError, TaggedError, matchError } from 'errore'
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 { isError, isOk } from 'errore'
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 { tryFn, tryAsync } from 'errore'
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 { map, mapError, andThen, tap } from 'errore'
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 { map, andThen, mapError, isError } from 'errore'
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 { unwrap, unwrapOr, match, partition } from 'errore'
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 { TaggedError, matchError, matchErrorPartial } from 'errore'
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
- class Base extends Error {
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 Base;
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 Base;
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> = Error & {
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> = Error & {
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
- class Base extends Error {
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 Base;
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 Base;
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
- // Annotate the CommonJS export names for ESM import in node:
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.5.2",
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",