go-go-try 7.2.0 → 7.3.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/src/goTry.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type { Result } from './types.js'
2
+ import { success, failure } from './result-helpers.js'
3
+ import { isPromise, getErrorMessage } from './internals.js'
4
+
5
+ /**
6
+ * Executes a function, promise, or value and returns a Result type.
7
+ * If an error occurs, it returns a Failure with the error message as a string.
8
+ *
9
+ * @template T The type of the successful result
10
+ * @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
11
+ * @returns {Result<string, T> | Promise<Result<string, T>>} A Result type or a Promise of a Result type
12
+ *
13
+ * @example
14
+ * // With a value
15
+ * const [err, result] = goTry(42);
16
+ *
17
+ * @example
18
+ * // With a function
19
+ * const [err, result] = goTry(() => JSON.parse('{"key": "value"}'));
20
+ *
21
+ * @example
22
+ * // With a promise
23
+ * const [err, result] = await goTry(fetch('https://api.example.com/data'));
24
+ */
25
+ export function goTry<T>(fn: () => never): Result<string, never>
26
+ export function goTry<T>(fn: () => Promise<T>): Promise<Result<string, T>>
27
+ export function goTry<T>(promise: Promise<T>): Promise<Result<string, T>>
28
+ export function goTry<T>(fn: () => T): Result<string, T>
29
+ export function goTry<T>(value: T): Result<string, T>
30
+ export function goTry<T>(
31
+ value: T | Promise<T> | (() => T | Promise<T>),
32
+ ): Result<string, T> | Promise<Result<string, T>> {
33
+ try {
34
+ const result =
35
+ typeof value === 'function' ? (value as () => T | Promise<T>)() : value
36
+ if (isPromise<T>(result)) {
37
+ return result
38
+ .then((resolvedValue) => success<T>(resolvedValue))
39
+ .catch((err) => failure<string>(getErrorMessage(err)))
40
+ }
41
+ return success<T>(result)
42
+ } catch (err) {
43
+ return failure<string>(getErrorMessage(err))
44
+ }
45
+ }
@@ -0,0 +1,141 @@
1
+ import type { GoTryAllOptions } from './types.js'
2
+ import { isError, getErrorMessage, type PromiseFactory } from './internals.js'
3
+
4
+ async function runWithConcurrency<T extends readonly unknown[]>(
5
+ items: { [K in keyof T]: Promise<T[K]> | PromiseFactory<T[K]> },
6
+ concurrency: number,
7
+ ): Promise<PromiseSettledResult<T[number]>[]> {
8
+ if (items.length === 0) {
9
+ return []
10
+ }
11
+
12
+ // Auto-detect factory mode by checking if first item is a function
13
+ const isFactoryMode = typeof items[0] === 'function'
14
+
15
+ // concurrency of 0 means unlimited (run all in parallel)
16
+ if (!isFactoryMode && (concurrency <= 0)) {
17
+ return Promise.allSettled(items as Promise<T[number]>[])
18
+ }
19
+
20
+ const results: PromiseSettledResult<T[number]>[] = new Array(items.length)
21
+ let index = 0
22
+
23
+ async function worker(): Promise<void> {
24
+ while (index < items.length) {
25
+ const currentIndex = index++
26
+ try {
27
+ const item = items[currentIndex]
28
+ // If factory mode, call the function; otherwise await the promise directly
29
+ const value = isFactoryMode
30
+ ? await (item as PromiseFactory<T[number]>)()
31
+ : await (item as Promise<T[number]>)
32
+ results[currentIndex] = { status: 'fulfilled', value }
33
+ } catch (reason) {
34
+ results[currentIndex] = { status: 'rejected', reason }
35
+ }
36
+ }
37
+ }
38
+
39
+ // Determine number of workers
40
+ const workerCount = concurrency <= 0 ? items.length : Math.min(concurrency, items.length)
41
+
42
+ // Start workers
43
+ const workers: Promise<void>[] = []
44
+ for (let i = 0; i < workerCount; i++) {
45
+ workers.push(worker())
46
+ }
47
+
48
+ await Promise.all(workers)
49
+ return results
50
+ }
51
+
52
+ /**
53
+ * Executes multiple promises or factory functions in parallel (or with limited concurrency)
54
+ * and returns a tuple of [errors, results]. Unlike Promise.all, this doesn't fail fast -
55
+ * it waits for all promises to settle.
56
+ *
57
+ * Accepts either:
58
+ * - An array of promises (for simple parallel execution)
59
+ * - An array of factory functions that return promises (for lazy execution with concurrency control)
60
+ *
61
+ * @template T The tuple type of all promise results
62
+ * @param {readonly [...{ [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) }]} items - Array of promises or factories
63
+ * @param {GoTryAllOptions} options - Optional configuration
64
+ * @returns {Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]>}
65
+ * A tuple where the first element is a tuple of errors (or undefined) and
66
+ * the second element is a tuple of results (or undefined), preserving input order
67
+ *
68
+ * @example
69
+ * // Run all in parallel (default) - with promises
70
+ * const [errors, results] = await goTryAll([
71
+ * fetchUser(1),
72
+ * fetchUser(2),
73
+ * fetchUser(3)
74
+ * ])
75
+ *
76
+ * @example
77
+ * // Limit concurrency with factory functions (lazy execution)
78
+ * const [errors, results] = await goTryAll([
79
+ * () => fetchUser(1), // Only called when a slot is available
80
+ * () => fetchUser(2), // Only called when a slot is available
81
+ * () => fetchUser(3), // Only called when a slot is available
82
+ * ], { concurrency: 2 })
83
+ */
84
+ export async function goTryAll<T extends readonly unknown[]>(
85
+ items: { [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) },
86
+ options?: GoTryAllOptions,
87
+ ): Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]> {
88
+ const settled = await runWithConcurrency(items, options?.concurrency ?? 0)
89
+
90
+ const errors = [] as { [K in keyof T]: string | undefined }
91
+ const results = [] as { [K in keyof T]: T[K] | undefined }
92
+
93
+ for (let i = 0; i < settled.length; i++) {
94
+ const item = settled[i]!
95
+ if (item.status === 'fulfilled') {
96
+ ;(errors as (string | undefined)[])[i] = undefined
97
+ ;(results as unknown[])[i] = (item as PromiseFulfilledResult<T[number]>).value
98
+ } else {
99
+ ;(errors as (string | undefined)[])[i] = getErrorMessage((item as PromiseRejectedResult).reason)
100
+ ;(results as unknown[])[i] = undefined
101
+ }
102
+ }
103
+
104
+ return [errors, results]
105
+ }
106
+
107
+ /**
108
+ * Like `goTryAll`, but returns raw Error objects instead of error messages.
109
+ *
110
+ * @template T The tuple type of all promise results
111
+ * @param {readonly [...{ [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) }]} items - Array of promises or factories
112
+ * @param {GoTryAllOptions} options - Optional configuration
113
+ * @returns {Promise<[{ [K in keyof T]: Error | undefined }, { [K in keyof T]: T[K] | undefined }]>}
114
+ * A tuple where the first element is a tuple of Error objects (or undefined) and
115
+ * the second element is a tuple of results (or undefined), preserving input order
116
+ */
117
+ export async function goTryAllRaw<T extends readonly unknown[]>(
118
+ items: { [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) },
119
+ options?: GoTryAllOptions,
120
+ ): Promise<[{ [K in keyof T]: Error | undefined }, { [K in keyof T]: T[K] | undefined }]> {
121
+ const settled = await runWithConcurrency(items, options?.concurrency ?? 0)
122
+
123
+ const errors = [] as { [K in keyof T]: Error | undefined }
124
+ const results = [] as { [K in keyof T]: T[K] | undefined }
125
+
126
+ for (let i = 0; i < settled.length; i++) {
127
+ const item = settled[i]!
128
+ if (item.status === 'fulfilled') {
129
+ ;(errors as (Error | undefined)[])[i] = undefined
130
+ ;(results as unknown[])[i] = (item as PromiseFulfilledResult<T[number]>).value
131
+ } else {
132
+ const reason = (item as PromiseRejectedResult).reason
133
+ ;(errors as (Error | undefined)[])[i] = isError(reason)
134
+ ? reason
135
+ : new Error(String(reason))
136
+ ;(results as unknown[])[i] = undefined
137
+ }
138
+ }
139
+
140
+ return [errors, results]
141
+ }
package/src/goTryOr.ts ADDED
@@ -0,0 +1,55 @@
1
+ import type { ResultWithDefault } from './types.js'
2
+ import { success } from './result-helpers.js'
3
+ import { isPromise, getErrorMessage, resolveDefault } from './internals.js'
4
+
5
+ /**
6
+ * Executes a function, promise, or value and returns a Result type with a fallback default.
7
+ * If an error occurs, it returns the error message and the default value.
8
+ *
9
+ * @template T The type of the successful result
10
+ * @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
11
+ * @param {T | (() => T)} defaultValue - The default value or a function to compute it (only called on failure)
12
+ * @returns {ResultWithDefault<string, T> | Promise<ResultWithDefault<string, T>>} A tuple of [error, value] or Promise thereof
13
+ *
14
+ * @example
15
+ * // With a static default
16
+ * const [err, config] = goTryOr(() => JSON.parse('invalid'), { port: 3000 })
17
+ * // err is the error message, config is { port: 3000 }
18
+ *
19
+ * @example
20
+ * // With a computed default (lazy evaluation)
21
+ * const [err, user] = await goTryOr(fetchUser(id), () => ({
22
+ * id: 'anonymous',
23
+ * name: 'Guest'
24
+ * }))
25
+ */
26
+ export function goTryOr<T>(fn: () => never, defaultValue: T | (() => T)): ResultWithDefault<string, T>
27
+ export function goTryOr<T>(
28
+ fn: () => Promise<T>,
29
+ defaultValue: T | (() => T),
30
+ ): Promise<ResultWithDefault<string, T>>
31
+ export function goTryOr<T>(
32
+ promise: Promise<T>,
33
+ defaultValue: T | (() => T),
34
+ ): Promise<ResultWithDefault<string, T>>
35
+ export function goTryOr<T>(fn: () => T, defaultValue: T | (() => T)): ResultWithDefault<string, T>
36
+ export function goTryOr<T>(value: T, defaultValue: T | (() => T)): ResultWithDefault<string, T>
37
+ export function goTryOr<T>(
38
+ value: T | Promise<T> | (() => T | Promise<T>),
39
+ defaultValue: T | (() => T),
40
+ ): ResultWithDefault<string, T> | Promise<ResultWithDefault<string, T>> {
41
+ try {
42
+ const result =
43
+ typeof value === 'function' ? (value as () => T | Promise<T>)() : value
44
+
45
+ if (isPromise<T>(result)) {
46
+ return result
47
+ .then((resolvedValue) => success<T>(resolvedValue))
48
+ .catch((err) => [getErrorMessage(err), resolveDefault(defaultValue)] as const)
49
+ }
50
+
51
+ return success<T>(result)
52
+ } catch (err) {
53
+ return [getErrorMessage(err), resolveDefault(defaultValue)] as const
54
+ }
55
+ }
@@ -0,0 +1,89 @@
1
+ import type { Result, ErrorConstructor } from './types.js'
2
+ import { success, failure } from './result-helpers.js'
3
+ import { isPromise, isError } from './internals.js'
4
+
5
+ /**
6
+ * Executes a function, promise, or value and returns a Result type.
7
+ * If an error occurs, it returns a Failure with the raw error object.
8
+ *
9
+ * @template T The type of the successful result
10
+ * @template E The type of the error, defaults to Error
11
+ * @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
12
+ * @param {ErrorConstructor<E>} [ErrorClass] - Optional error constructor to wrap caught errors
13
+ * @returns {Result<E, T> | Promise<Result<E, T>>} A Result type or a Promise of a Result type
14
+ *
15
+ * @example
16
+ * // With a value
17
+ * const [err, result] = goTryRaw(42);
18
+ *
19
+ * @example
20
+ * // With a function
21
+ * const [err, result] = goTryRaw(() => JSON.parse('{"key": "value"}'));
22
+ *
23
+ * @example
24
+ * // With a promise
25
+ * const [err, result] = await goTryRaw(fetch('https://api.example.com/data'));
26
+ *
27
+ * @example
28
+ * // With tagged error for discriminated unions
29
+ * const DatabaseError = taggedError('DatabaseError');
30
+ * const [err, result] = await goTryRaw(fetchData(), DatabaseError);
31
+ * // err is InstanceType<typeof DatabaseError> | undefined
32
+ */
33
+ export function goTryRaw<T, E = Error>(fn: () => never): Result<E, never>
34
+ export function goTryRaw<T, E = Error>(fn: () => never, ErrorClass: ErrorConstructor<E>): Result<E, never>
35
+ export function goTryRaw<T, E = Error>(
36
+ fn: () => Promise<T>,
37
+ ): Promise<Result<E, T>>
38
+ export function goTryRaw<T, E = Error>(
39
+ fn: () => Promise<T>,
40
+ ErrorClass: ErrorConstructor<E>,
41
+ ): Promise<Result<E, T>>
42
+ export function goTryRaw<T, E = Error>(
43
+ promise: Promise<T>,
44
+ ): Promise<Result<E, T>>
45
+ export function goTryRaw<T, E = Error>(
46
+ promise: Promise<T>,
47
+ ErrorClass: ErrorConstructor<E>,
48
+ ): Promise<Result<E, T>>
49
+ export function goTryRaw<T, E = Error>(fn: () => T): Result<E, T>
50
+ export function goTryRaw<T, E = Error>(fn: () => T, ErrorClass: ErrorConstructor<E>): Result<E, T>
51
+ export function goTryRaw<T, E = Error>(value: T): Result<E, T>
52
+ export function goTryRaw<T, E = Error>(value: T, ErrorClass: ErrorConstructor<E>): Result<E, T>
53
+ export function goTryRaw<T, E = Error>(
54
+ value: T | Promise<T> | (() => T | Promise<T>),
55
+ ErrorClass?: ErrorConstructor<E>,
56
+ ): Result<E, T> | Promise<Result<E, T>> {
57
+ // Helper to wrap error in the provided class or return as-is
58
+ const wrapError = (err: unknown): E => {
59
+ if (ErrorClass) {
60
+ if (err === undefined) {
61
+ return new ErrorClass('undefined')
62
+ }
63
+ if (isError(err)) {
64
+ return new ErrorClass(err.message, { cause: err })
65
+ }
66
+ return new ErrorClass(String(err))
67
+ }
68
+ // Default behavior: return raw error
69
+ if (err === undefined) {
70
+ return new Error('undefined') as unknown as E
71
+ }
72
+ return (
73
+ isError(err) ? err : new Error(String(err))
74
+ ) as unknown as E
75
+ }
76
+
77
+ try {
78
+ const result =
79
+ typeof value === 'function' ? (value as () => T | Promise<T>)() : value
80
+ if (isPromise<T>(result)) {
81
+ return result
82
+ .then((resolvedValue) => success<T>(resolvedValue))
83
+ .catch((err) => failure<E>(wrapError(err)))
84
+ }
85
+ return success<T>(result)
86
+ } catch (err) {
87
+ return failure<E>(wrapError(err))
88
+ }
89
+ }
package/src/index.test.ts CHANGED
@@ -1,15 +1,10 @@
1
1
  import { attest } from '@ark/attest'
2
2
  import { assert, describe, test } from 'vitest'
3
-
4
- // Helper for exhaustive switch checks - if this function is called,
5
- // it means we forgot to handle a case in a switch statement
6
- function assertNever(value: never): never {
7
- throw new Error(`Unhandled case: ${String(value)}`)
8
- }
9
3
  import {
10
4
  type Result,
11
- type TaggedInstance,
12
5
  type TaggedUnion,
6
+ assert as assertTry,
7
+ assertNever,
13
8
  failure,
14
9
  goTry,
15
10
  goTryAll,
@@ -363,6 +358,190 @@ describe('edge cases', () => {
363
358
  })
364
359
  })
365
360
 
361
+ describe('assert helper', () => {
362
+ test('does not throw when condition is true', () => {
363
+ // Should not throw
364
+ assertTry(true, 'should not throw')
365
+ assertTry(1 > 0, new Error('should not throw'))
366
+ assertTry('truthy', 'should not throw')
367
+ })
368
+
369
+ test('throws with string message when condition is false', () => {
370
+ try {
371
+ assertTry(false, 'custom error message')
372
+ // Should not reach here
373
+ assert.equal(true, false)
374
+ } catch (err) {
375
+ assert.ok(err instanceof Error)
376
+ assert.equal((err as Error).message, 'custom error message')
377
+ }
378
+ })
379
+
380
+ test('throws with Error instance when condition is false', () => {
381
+ const customError = new Error('custom error instance')
382
+ try {
383
+ assertTry(false, customError)
384
+ // Should not reach here
385
+ assert.equal(true, false)
386
+ } catch (err) {
387
+ assert.equal(err, customError)
388
+ }
389
+ })
390
+
391
+ test('throws with tagged error when condition is false', () => {
392
+ const DatabaseError = taggedError('DatabaseError')
393
+ try {
394
+ assertTry(false, new DatabaseError('database connection failed'))
395
+ // Should not reach here
396
+ assert.equal(true, false)
397
+ } catch (err) {
398
+ assert.ok(err instanceof DatabaseError)
399
+ assert.equal((err as InstanceType<typeof DatabaseError>)._tag, 'DatabaseError')
400
+ assert.equal((err as Error).message, 'database connection failed')
401
+ }
402
+ })
403
+
404
+ test('type narrowing works with Result types using err === undefined', () => {
405
+ const [err, value] = goTry(() => 'success')
406
+
407
+ // Before assert: err is string | undefined, value is string | undefined
408
+ attest<string | undefined>(err)
409
+ attest<string | undefined>(value)
410
+
411
+ // Using err === undefined provides the best type narrowing
412
+ assertTry(err === undefined, 'should have no error')
413
+
414
+ // TypeScript now knows err is undefined and value is string
415
+ attest<undefined>(err)
416
+ attest<string>(value)
417
+ assert.equal(value, 'success')
418
+ })
419
+
420
+ test('type narrowing works with err === undefined check', () => {
421
+ const [err, value] = goTryRaw(() => ({ id: 1, name: 'test' }))
422
+
423
+ // Before assert
424
+ attest<Error | undefined>(err)
425
+ attest<{ id: number; name: string } | undefined>(value)
426
+
427
+ // Using err === undefined pattern
428
+ assertTry(err === undefined, new Error('should have no error'))
429
+
430
+ // After assert: TypeScript knows err is undefined, value is defined
431
+ attest<{ id: number; name: string }>(value)
432
+ assert.deepEqual(value, { id: 1, name: 'test' })
433
+ })
434
+
435
+ test('type narrowing works with tagged errors', () => {
436
+ const DatabaseError = taggedError('DatabaseError')
437
+ const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), DatabaseError)
438
+
439
+ // Before assert
440
+ attest<InstanceType<typeof DatabaseError> | undefined>(err)
441
+ attest<{ id: string; name: string } | undefined>(user)
442
+
443
+ // Use assert with tagged error
444
+ assertTry(err === undefined, new DatabaseError('Failed to fetch user'))
445
+
446
+ // After assert: TypeScript knows err is undefined, user is defined
447
+ attest<{ id: string; name: string }>(user)
448
+ assert.deepEqual(user, { id: '123', name: 'John' })
449
+ })
450
+
451
+ test('reduces boilerplate compared to if(err) throw', () => {
452
+ const DatabaseError = taggedError('DatabaseError')
453
+
454
+ function fetchUserOldStyle(): Result<InstanceType<typeof DatabaseError>, { id: string }> {
455
+ const [err, user] = goTryRaw(() => ({ id: '123' }), DatabaseError)
456
+ if (err) return failure(err) // Old style
457
+ return [undefined, user] as const
458
+ }
459
+
460
+ function fetchUserNewStyle(): Result<InstanceType<typeof DatabaseError>, { id: string }> {
461
+ const [err, user] = goTryRaw(() => ({ id: '123' }), DatabaseError)
462
+ assertTry(err === undefined, new DatabaseError('Failed to fetch user'))
463
+ // TypeScript now knows user is defined
464
+ return [undefined, user] as const
465
+ }
466
+
467
+ const [err1, user1] = fetchUserOldStyle()
468
+ const [err2, user2] = fetchUserNewStyle()
469
+
470
+ assert.equal(err1, undefined)
471
+ assert.equal(err2, undefined)
472
+ assert.deepEqual(user1, { id: '123' })
473
+ assert.deepEqual(user2, { id: '123' })
474
+ })
475
+
476
+ test('works with falsy values as condition', () => {
477
+ // 0 is falsy - should throw
478
+ assert.throws(() => assertTry(0, 'zero is falsy'))
479
+
480
+ // empty string is falsy - should throw
481
+ assert.throws(() => assertTry('', 'empty string is falsy'))
482
+
483
+ // null is falsy - should throw
484
+ assert.throws(() => assertTry(null, 'null is falsy'))
485
+
486
+ // undefined is falsy - should throw
487
+ assert.throws(() => assertTry(undefined, 'undefined is falsy'))
488
+
489
+ // NaN is falsy - should throw
490
+ assert.throws(() => assertTry(Number.NaN, 'NaN is falsy'))
491
+ })
492
+
493
+ test('works with truthy values as condition', () => {
494
+ // non-zero number is truthy
495
+ assertTry(1, new Error('one is truthy'))
496
+
497
+ // non-empty string is truthy
498
+ assertTry('hello', new Error('string is truthy'))
499
+
500
+ // object is truthy
501
+ assertTry({}, new Error('object is truthy'))
502
+
503
+ // array is truthy
504
+ assertTry([], new Error('array is truthy'))
505
+
506
+ // true is truthy
507
+ assertTry(true, new Error('true is truthy'))
508
+ })
509
+
510
+ test('works with ErrorClass and message (shorter syntax)', () => {
511
+ const ValidationError = taggedError('ValidationError')
512
+
513
+ // Should not throw when condition is true
514
+ assertTry(5 > 0, ValidationError, 'Value must be positive')
515
+
516
+ // Should throw with instantiated error when condition is false
517
+ try {
518
+ assertTry(-1 > 0, ValidationError, 'Value must be positive')
519
+ assert.equal(true, false) // Should not reach here
520
+ } catch (err) {
521
+ assert.ok(err instanceof ValidationError)
522
+ assert.equal((err as InstanceType<typeof ValidationError>)._tag, 'ValidationError')
523
+ assert.equal((err as Error).message, 'Value must be positive')
524
+ }
525
+ })
526
+
527
+ test('shorter syntax with tagged errors provides type narrowing', () => {
528
+ const DatabaseError = taggedError('DatabaseError')
529
+ const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), DatabaseError)
530
+
531
+ // Before assert
532
+ attest<InstanceType<typeof DatabaseError> | undefined>(err)
533
+ attest<{ id: string; name: string } | undefined>(user)
534
+
535
+ // Use assert with shorter syntax
536
+ assertTry(err === undefined, DatabaseError, 'Failed to fetch user')
537
+
538
+ // After assert: TypeScript knows err is undefined, user is defined
539
+ attest<undefined>(err)
540
+ attest<{ id: string; name: string }>(user)
541
+ assert.deepEqual(user, { id: '123', name: 'John' })
542
+ })
543
+ })
544
+
366
545
  describe('goTry type tests', () => {
367
546
  test('synchronous function returns correct types', () => {
368
547
  const fn = () => 'value'
@@ -1080,7 +1259,7 @@ describe('taggedError', () => {
1080
1259
  case 'ValidationError':
1081
1260
  return `VAL: ${err.message}`
1082
1261
  default:
1083
- return `UNK: ${err.message}`
1262
+ return assertNever(err)
1084
1263
  }
1085
1264
  }
1086
1265
 
@@ -1149,18 +1328,6 @@ describe('taggedError type tests', () => {
1149
1328
  })
1150
1329
 
1151
1330
 
1152
- describe('TaggedInstance type helper', () => {
1153
- test('extracts instance type from tagged error class', () => {
1154
- const DatabaseError = taggedError('DatabaseError')
1155
- type DbError = TaggedInstance<typeof DatabaseError>
1156
-
1157
- // DbError should be the instance type
1158
- const err: DbError = new DatabaseError('fail')
1159
- assert.equal(err._tag, 'DatabaseError')
1160
- assert.equal(err.message, 'fail')
1161
- })
1162
- })
1163
-
1164
1331
  describe('TaggedUnion type helper', () => {
1165
1332
  test('creates union type from multiple error classes', () => {
1166
1333
  const DatabaseError = taggedError('DatabaseError')
@@ -1189,7 +1356,7 @@ describe('TaggedUnion type helper', () => {
1189
1356
  case 'ValidationError':
1190
1357
  return `VAL: ${err.message}`
1191
1358
  default:
1192
- return `UNK: ${err.message}`
1359
+ return assertNever(err)
1193
1360
  }
1194
1361
  }
1195
1362