go-go-try 7.3.0 → 7.4.1

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/index.test.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  isSuccess,
16
16
  success,
17
17
  taggedError,
18
+ UnknownError,
18
19
  } from './index.js'
19
20
 
20
21
  test(`value returned by callback is used when callback doesn't throw`, async () => {
@@ -319,8 +320,12 @@ describe('edge cases', () => {
319
320
  assert.equal(value1, undefined)
320
321
  assert.equal(value2, undefined)
321
322
  assert.equal(err1, 'custom error')
322
- assert.equal(err2 instanceof CustomError, true)
323
- assert.equal((err2 as CustomError).code, 500)
323
+ // goTryRaw now wraps errors in UnknownError by default
324
+ assert.equal(err2 instanceof UnknownError, true)
325
+ assert.equal((err2 as InstanceType<typeof UnknownError>)?._tag, 'UnknownError')
326
+ assert.equal(err2?.message, 'custom error')
327
+ // Original error is preserved in cause
328
+ assert.equal(((err2 as unknown as { cause?: unknown })?.cause as CustomError)?.code, 500)
324
329
  })
325
330
 
326
331
  test('throwing a string', () => {
@@ -367,38 +372,41 @@ describe('assert helper', () => {
367
372
  })
368
373
 
369
374
  test('throws with string message when condition is false', () => {
375
+ let caught = false
370
376
  try {
371
377
  assertTry(false, 'custom error message')
372
- // Should not reach here
373
- assert.equal(true, false)
374
378
  } catch (err) {
379
+ caught = true
375
380
  assert.ok(err instanceof Error)
376
381
  assert.equal((err as Error).message, 'custom error message')
377
382
  }
383
+ assert.equal(caught, true)
378
384
  })
379
385
 
380
386
  test('throws with Error instance when condition is false', () => {
381
387
  const customError = new Error('custom error instance')
388
+ let caught = false
382
389
  try {
383
390
  assertTry(false, customError)
384
- // Should not reach here
385
- assert.equal(true, false)
386
391
  } catch (err) {
392
+ caught = true
387
393
  assert.equal(err, customError)
388
394
  }
395
+ assert.equal(caught, true)
389
396
  })
390
397
 
391
398
  test('throws with tagged error when condition is false', () => {
392
399
  const DatabaseError = taggedError('DatabaseError')
400
+ let caught = false
393
401
  try {
394
402
  assertTry(false, new DatabaseError('database connection failed'))
395
- // Should not reach here
396
- assert.equal(true, false)
397
403
  } catch (err) {
404
+ caught = true
398
405
  assert.ok(err instanceof DatabaseError)
399
406
  assert.equal((err as InstanceType<typeof DatabaseError>)._tag, 'DatabaseError')
400
407
  assert.equal((err as Error).message, 'database connection failed')
401
408
  }
409
+ assert.equal(caught, true)
402
410
  })
403
411
 
404
412
  test('type narrowing works with Result types using err === undefined', () => {
@@ -434,7 +442,7 @@ describe('assert helper', () => {
434
442
 
435
443
  test('type narrowing works with tagged errors', () => {
436
444
  const DatabaseError = taggedError('DatabaseError')
437
- const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), DatabaseError)
445
+ const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), { errorClass: DatabaseError })
438
446
 
439
447
  // Before assert
440
448
  attest<InstanceType<typeof DatabaseError> | undefined>(err)
@@ -452,13 +460,13 @@ describe('assert helper', () => {
452
460
  const DatabaseError = taggedError('DatabaseError')
453
461
 
454
462
  function fetchUserOldStyle(): Result<InstanceType<typeof DatabaseError>, { id: string }> {
455
- const [err, user] = goTryRaw(() => ({ id: '123' }), DatabaseError)
463
+ const [err, user] = goTryRaw(() => ({ id: '123' }), { errorClass: DatabaseError })
456
464
  if (err) return failure(err) // Old style
457
465
  return [undefined, user] as const
458
466
  }
459
467
 
460
468
  function fetchUserNewStyle(): Result<InstanceType<typeof DatabaseError>, { id: string }> {
461
- const [err, user] = goTryRaw(() => ({ id: '123' }), DatabaseError)
469
+ const [err, user] = goTryRaw(() => ({ id: '123' }), { errorClass: DatabaseError })
462
470
  assertTry(err === undefined, new DatabaseError('Failed to fetch user'))
463
471
  // TypeScript now knows user is defined
464
472
  return [undefined, user] as const
@@ -526,7 +534,7 @@ describe('assert helper', () => {
526
534
 
527
535
  test('shorter syntax with tagged errors provides type narrowing', () => {
528
536
  const DatabaseError = taggedError('DatabaseError')
529
- const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), DatabaseError)
537
+ const [err, user] = goTryRaw(() => ({ id: '123', name: 'John' }), { errorClass: DatabaseError })
530
538
 
531
539
  // Before assert
532
540
  attest<InstanceType<typeof DatabaseError> | undefined>(err)
@@ -1099,7 +1107,7 @@ describe('goTryAllRaw', () => {
1099
1107
  assert.deepEqual(results, ['a', 42, true])
1100
1108
  })
1101
1109
 
1102
- test('returns Error objects for failed promises', async () => {
1110
+ test('wraps non-tagged errors in UnknownError', async () => {
1103
1111
  const [errors, results] = await goTryAllRaw([
1104
1112
  Promise.resolve('success'),
1105
1113
  Promise.reject(new Error('fail1')),
@@ -1107,6 +1115,8 @@ describe('goTryAllRaw', () => {
1107
1115
  ])
1108
1116
 
1109
1117
  assert.equal(errors[0], undefined)
1118
+ assert.ok(errors[1] instanceof UnknownError)
1119
+ assert.equal(errors[1]?._tag, 'UnknownError')
1110
1120
  assert.equal(errors[1]?.message, 'fail1')
1111
1121
  assert.equal(errors[2], undefined)
1112
1122
 
@@ -1115,15 +1125,44 @@ describe('goTryAllRaw', () => {
1115
1125
  assert.equal(results[2], 42)
1116
1126
  })
1117
1127
 
1118
- test('converts non-Error rejections to Error objects', async () => {
1128
+ test('tagged errors pass through unchanged', async () => {
1129
+ const DatabaseError = taggedError('DatabaseError')
1130
+ const NetworkError = taggedError('NetworkError')
1131
+
1132
+ const [errors] = await goTryAllRaw([
1133
+ Promise.reject(new DatabaseError('db failed')),
1134
+ Promise.reject(new NetworkError('network timeout')),
1135
+ Promise.reject(new Error('plain error')),
1136
+ ])
1137
+
1138
+ // Tagged errors pass through
1139
+ assert.ok(errors[0] instanceof DatabaseError)
1140
+ assert.equal(errors[0]?._tag, 'DatabaseError')
1141
+ assert.equal(errors[0]?.message, 'db failed')
1142
+ assert.ok(errors[1] instanceof NetworkError)
1143
+ assert.equal(errors[1]?._tag, 'NetworkError')
1144
+ assert.equal(errors[1]?.message, 'network timeout')
1145
+ // Non-tagged errors get wrapped in UnknownError
1146
+ assert.ok(errors[2] instanceof UnknownError)
1147
+ assert.equal(errors[2]?._tag, 'UnknownError')
1148
+ assert.equal(errors[2]?.message, 'plain error')
1149
+ })
1150
+
1151
+ test('converts non-Error rejections to UnknownError objects', async () => {
1119
1152
  const [errors] = await goTryAllRaw([
1120
1153
  Promise.reject('string error'),
1121
1154
  Promise.reject(42),
1122
1155
  Promise.reject(undefined),
1123
1156
  ])
1124
1157
 
1158
+ assert.ok(errors[0] instanceof UnknownError)
1159
+ assert.equal(errors[0]?._tag, 'UnknownError')
1125
1160
  assert.equal(errors[0]?.message, 'string error')
1161
+ assert.ok(errors[1] instanceof UnknownError)
1162
+ assert.equal(errors[1]?._tag, 'UnknownError')
1126
1163
  assert.equal(errors[1]?.message, '42')
1164
+ assert.ok(errors[2] instanceof UnknownError)
1165
+ assert.equal(errors[2]?._tag, 'UnknownError')
1127
1166
  assert.equal(errors[2]?.message, 'undefined')
1128
1167
  })
1129
1168
 
@@ -1133,6 +1172,60 @@ describe('goTryAllRaw', () => {
1133
1172
  assert.deepEqual(errors, [])
1134
1173
  assert.deepEqual(results, [])
1135
1174
  })
1175
+
1176
+ test('errorClass wraps all errors', async () => {
1177
+ const DatabaseError = taggedError('DatabaseError')
1178
+ const NetworkError = taggedError('NetworkError')
1179
+
1180
+ const [errors] = await goTryAllRaw([
1181
+ Promise.reject(new DatabaseError('db error')),
1182
+ Promise.reject(new NetworkError('network error')),
1183
+ Promise.reject(new Error('plain error')),
1184
+ ], { errorClass: DatabaseError })
1185
+
1186
+ // All errors should be wrapped in DatabaseError
1187
+ assert.ok(errors[0] instanceof DatabaseError)
1188
+ assert.equal(errors[0]?._tag, 'DatabaseError')
1189
+ assert.ok(errors[1] instanceof DatabaseError)
1190
+ assert.equal(errors[1]?._tag, 'DatabaseError')
1191
+ assert.ok(errors[2] instanceof DatabaseError)
1192
+ assert.equal(errors[2]?._tag, 'DatabaseError')
1193
+ })
1194
+
1195
+ test('systemErrorClass only wraps non-tagged errors', async () => {
1196
+ const DatabaseError = taggedError('DatabaseError')
1197
+ const NetworkError = taggedError('NetworkError')
1198
+ const SystemError = taggedError('SystemError')
1199
+
1200
+ const [errors] = await goTryAllRaw([
1201
+ Promise.reject(new DatabaseError('db error')),
1202
+ Promise.reject(new NetworkError('network error')),
1203
+ Promise.reject(new Error('plain error')),
1204
+ ], { systemErrorClass: SystemError })
1205
+
1206
+ // Tagged errors pass through
1207
+ assert.ok(errors[0] instanceof DatabaseError)
1208
+ assert.equal(errors[0]?._tag, 'DatabaseError')
1209
+ assert.ok(errors[1] instanceof NetworkError)
1210
+ assert.equal(errors[1]?._tag, 'NetworkError')
1211
+ // Non-tagged error wrapped in SystemError
1212
+ assert.ok(errors[2] instanceof SystemError)
1213
+ assert.equal(errors[2]?._tag, 'SystemError')
1214
+ })
1215
+
1216
+ test('concurrency option works with errorClass', async () => {
1217
+ const DatabaseError = taggedError('DatabaseError')
1218
+
1219
+ const [errors] = await goTryAllRaw([
1220
+ Promise.reject(new Error('error 1')),
1221
+ Promise.reject(new Error('error 2')),
1222
+ Promise.reject(new Error('error 3')),
1223
+ ], { concurrency: 2, errorClass: DatabaseError })
1224
+
1225
+ assert.ok(errors[0] instanceof DatabaseError)
1226
+ assert.ok(errors[1] instanceof DatabaseError)
1227
+ assert.ok(errors[2] instanceof DatabaseError)
1228
+ })
1136
1229
  })
1137
1230
 
1138
1231
 
@@ -1187,8 +1280,8 @@ describe('taggedError', () => {
1187
1280
  }
1188
1281
 
1189
1282
  // Wrap in functions so goTryRaw can catch the errors
1190
- const [dbErr, dbResult] = goTryRaw(fetchFromDb, DatabaseError)
1191
- const [netErr, netResult] = goTryRaw(fetchFromNetwork, NetworkError)
1283
+ const [dbErr, dbResult] = goTryRaw(fetchFromDb, { errorClass: DatabaseError })
1284
+ const [netErr, netResult] = goTryRaw(fetchFromNetwork, { errorClass: NetworkError })
1192
1285
 
1193
1286
  // Type narrowing via discriminated union
1194
1287
  if (dbErr) {
@@ -1214,14 +1307,14 @@ describe('taggedError', () => {
1214
1307
  async function fetchUser(id: string): Promise<Result<AppError, { id: string; name: string }>> {
1215
1308
  const [dbErr, user] = await goTryRaw(
1216
1309
  Promise.resolve({ id, name: 'John' }),
1217
- DatabaseError,
1310
+ { errorClass: DatabaseError },
1218
1311
  )
1219
1312
  if (dbErr) return failure<AppError>(dbErr)
1220
1313
  return [undefined, user] as const
1221
1314
  }
1222
1315
 
1223
1316
  async function fetchData(): Promise<Result<AppError, string>> {
1224
- const [netErr, data] = await goTryRaw(Promise.resolve('data'), NetworkError)
1317
+ const [netErr, data] = await goTryRaw(Promise.resolve('data'), { errorClass: NetworkError })
1225
1318
  if (netErr) return failure<AppError>(netErr)
1226
1319
  return [undefined, data] as const
1227
1320
  }
@@ -1374,7 +1467,7 @@ describe('TaggedUnion type helper', () => {
1374
1467
  async function fetchData(): Promise<Result<AppError, string>> {
1375
1468
  const [err, data] = await goTryRaw(
1376
1469
  Promise.reject(new Error('timeout')),
1377
- NetworkError,
1470
+ { errorClass: NetworkError },
1378
1471
  )
1379
1472
  if (err) return failure<AppError>(err)
1380
1473
  return [undefined, data] as const
@@ -1398,14 +1491,14 @@ describe('inferred return types with tagged errors', () => {
1398
1491
  // First operation might fail with DatabaseError
1399
1492
  const [dbErr, user] = await goTryRaw(
1400
1493
  Promise.resolve({ id, name: 'John' }),
1401
- DatabaseError,
1494
+ { errorClass: DatabaseError },
1402
1495
  )
1403
1496
  if (dbErr) return failure(dbErr)
1404
1497
 
1405
1498
  // Second operation might fail with NetworkError
1406
1499
  const [netErr, enriched] = await goTryRaw(
1407
1500
  Promise.resolve({ ...user!, email: 'john@example.com' }),
1408
- NetworkError,
1501
+ { errorClass: NetworkError },
1409
1502
  )
1410
1503
  if (netErr) return failure(netErr)
1411
1504
 
@@ -1443,14 +1536,14 @@ describe('inferred return types with tagged errors', () => {
1443
1536
  // No explicit return type annotation
1444
1537
  function processConfig(input: string) {
1445
1538
  // Parse step
1446
- const [parseErr, parsed] = goTryRaw(() => JSON.parse(input), ParseError)
1539
+ const [parseErr, parsed] = goTryRaw(() => JSON.parse(input), { errorClass: ParseError })
1447
1540
  if (parseErr) return failure(parseErr)
1448
1541
 
1449
1542
  // Validate step
1450
1543
  const [validateErr, validated] = goTryRaw(() => {
1451
1544
  if (!parsed!.port) throw new Error('Missing port')
1452
1545
  return parsed as { port: number }
1453
- }, ValidateError)
1546
+ }, { errorClass: ValidateError })
1454
1547
  if (validateErr) return failure(validateErr)
1455
1548
 
1456
1549
  return [undefined, validated] as const
@@ -1481,19 +1574,19 @@ describe('inferred return types with tagged errors', () => {
1481
1574
  async function complexOperation(shouldFail: 'a' | 'b' | 'c' | 'none') {
1482
1575
  const [errA, valA] = await goTryRaw(
1483
1576
  shouldFail === 'a' ? Promise.reject(new Error('a')) : Promise.resolve('step1'),
1484
- ErrorA,
1577
+ { errorClass: ErrorA },
1485
1578
  )
1486
1579
  if (errA) return failure(errA)
1487
1580
 
1488
1581
  const [errB, valB] = await goTryRaw(
1489
1582
  shouldFail === 'b' ? Promise.reject(new Error('b')) : Promise.resolve('step2'),
1490
- ErrorB,
1583
+ { errorClass: ErrorB },
1491
1584
  )
1492
1585
  if (errB) return failure(errB)
1493
1586
 
1494
1587
  const [errC, valC] = await goTryRaw(
1495
1588
  shouldFail === 'c' ? Promise.reject(new Error('c')) : Promise.resolve('step3'),
1496
- ErrorC,
1589
+ { errorClass: ErrorC },
1497
1590
  )
1498
1591
  if (errC) return failure(errC)
1499
1592
 
@@ -1530,3 +1623,204 @@ describe('inferred return types with tagged errors', () => {
1530
1623
  )
1531
1624
  })
1532
1625
  })
1626
+
1627
+
1628
+ describe('UnknownError', () => {
1629
+ test('UnknownError is exported as a tagged error class', () => {
1630
+ const err = new UnknownError('something went wrong')
1631
+ assert.equal(err._tag, 'UnknownError')
1632
+ assert.equal(err.message, 'something went wrong')
1633
+ assert.equal(err.name, 'UnknownError')
1634
+ assert.ok(err instanceof Error)
1635
+ assert.ok(err instanceof UnknownError)
1636
+ })
1637
+
1638
+ test('UnknownError supports cause option', () => {
1639
+ const cause = new Error('original error')
1640
+ const err = new UnknownError('wrapped error', { cause })
1641
+ assert.equal(err.cause, cause)
1642
+ })
1643
+
1644
+ test('goTryRaw defaults to UnknownError for system errors', () => {
1645
+ const fn = () => {
1646
+ throw new Error('system error')
1647
+ }
1648
+
1649
+ const [err, value] = goTryRaw(fn)
1650
+
1651
+ assert.equal(value, undefined)
1652
+ assert.ok(err instanceof UnknownError)
1653
+ assert.equal(err._tag, 'UnknownError')
1654
+ assert.equal(err.message, 'system error')
1655
+ })
1656
+
1657
+ test('goTryRaw defaults to UnknownError for thrown strings', () => {
1658
+ const fn = () => {
1659
+ throw 'string error'
1660
+ }
1661
+
1662
+ const [err, value] = goTryRaw(fn)
1663
+
1664
+ assert.equal(value, undefined)
1665
+ assert.ok(err instanceof UnknownError)
1666
+ assert.equal(err._tag, 'UnknownError')
1667
+ assert.equal(err.message, 'string error')
1668
+ })
1669
+
1670
+ test('goTryRaw defaults to UnknownError for thrown undefined', () => {
1671
+ const fn = () => {
1672
+ throw undefined
1673
+ }
1674
+
1675
+ const [err, value] = goTryRaw(fn)
1676
+
1677
+ assert.equal(value, undefined)
1678
+ assert.ok(err instanceof UnknownError)
1679
+ assert.equal(err._tag, 'UnknownError')
1680
+ assert.equal(err.message, 'undefined')
1681
+ })
1682
+
1683
+ test('goTryRaw with async defaults to UnknownError', async () => {
1684
+ const promise = Promise.reject(new Error('async error'))
1685
+
1686
+ const [err, value] = await goTryRaw(promise)
1687
+
1688
+ assert.equal(value, undefined)
1689
+ assert.ok(err instanceof UnknownError)
1690
+ assert.equal(err._tag, 'UnknownError')
1691
+ assert.equal(err.message, 'async error')
1692
+ })
1693
+ })
1694
+
1695
+ describe('goTryRaw with options object', () => {
1696
+ test('errorClass wraps all errors including tagged ones', () => {
1697
+ const DatabaseError = taggedError('DatabaseError')
1698
+ const NetworkError = taggedError('NetworkError')
1699
+
1700
+ // When a DatabaseError is thrown, it gets wrapped in NetworkError
1701
+ const fn = () => {
1702
+ throw new DatabaseError('db connection failed')
1703
+ }
1704
+
1705
+ const [err, value] = goTryRaw(fn, { errorClass: NetworkError })
1706
+
1707
+ assert.equal(value, undefined)
1708
+ assert.ok(err instanceof NetworkError)
1709
+ assert.equal(err._tag, 'NetworkError')
1710
+ assert.equal(err.message, 'db connection failed')
1711
+ // Original error is preserved in cause
1712
+ assert.ok(err.cause instanceof DatabaseError)
1713
+ })
1714
+
1715
+ test('errorClass wraps non-tagged errors', () => {
1716
+ const NetworkError = taggedError('NetworkError')
1717
+
1718
+ const fn = () => {
1719
+ throw new Error('plain error')
1720
+ }
1721
+
1722
+ const [err, value] = goTryRaw(fn, { errorClass: NetworkError })
1723
+
1724
+ assert.equal(value, undefined)
1725
+ assert.ok(err instanceof NetworkError)
1726
+ assert.equal(err._tag, 'NetworkError')
1727
+ assert.equal(err.message, 'plain error')
1728
+ })
1729
+
1730
+ test('systemErrorClass only wraps non-tagged errors', () => {
1731
+ const DatabaseError = taggedError('DatabaseError')
1732
+ const SystemError = taggedError('SystemError')
1733
+
1734
+ // Tagged errors should pass through
1735
+ const fnTagged = () => {
1736
+ throw new DatabaseError('db error')
1737
+ }
1738
+
1739
+ const [err1, value1] = goTryRaw(fnTagged, { systemErrorClass: SystemError })
1740
+
1741
+ assert.equal(value1, undefined)
1742
+ assert.ok(err1 instanceof DatabaseError)
1743
+ assert.equal(err1._tag, 'DatabaseError')
1744
+
1745
+ // Non-tagged errors should be wrapped
1746
+ const fnPlain = () => {
1747
+ throw new Error('system error')
1748
+ }
1749
+
1750
+ const [err2, value2] = goTryRaw(fnPlain, { systemErrorClass: SystemError })
1751
+
1752
+ assert.equal(value2, undefined)
1753
+ assert.ok(err2 instanceof SystemError)
1754
+ assert.equal(err2._tag, 'SystemError')
1755
+ assert.equal(err2.message, 'system error')
1756
+ })
1757
+
1758
+ test('systemErrorClass defaults to UnknownError when not specified', () => {
1759
+ const fn = () => {
1760
+ throw new Error('plain error')
1761
+ }
1762
+
1763
+ const [err, value] = goTryRaw(fn, {})
1764
+
1765
+ assert.equal(value, undefined)
1766
+ assert.ok(err instanceof UnknownError)
1767
+ assert.equal(err._tag, 'UnknownError')
1768
+ })
1769
+
1770
+ test('async with options object', async () => {
1771
+ const DatabaseError = taggedError('DatabaseError')
1772
+
1773
+ const promise = Promise.reject(new Error('async error'))
1774
+
1775
+ const [err, value] = await goTryRaw(promise, { errorClass: DatabaseError })
1776
+
1777
+ assert.equal(value, undefined)
1778
+ assert.ok(err instanceof DatabaseError)
1779
+ assert.equal(err._tag, 'DatabaseError')
1780
+ })
1781
+
1782
+ })
1783
+
1784
+ describe('goTryRaw options type tests', () => {
1785
+ test('systemErrorClass preserves tagged errors', () => {
1786
+ const DatabaseError = taggedError('DatabaseError')
1787
+ const SystemError = taggedError('SystemError')
1788
+
1789
+ // Wrap in a function so goTryRaw can catch the error
1790
+ const [err, _value] = goTryRaw(() => {
1791
+ throw new DatabaseError('db error')
1792
+ }, { systemErrorClass: SystemError })
1793
+
1794
+ // Type is systemErrorClass since TypeScript cannot know which tagged errors
1795
+ // might be thrown at runtime (tagged errors pass through, others get wrapped)
1796
+ attest<InstanceType<typeof SystemError> | undefined>(err)
1797
+
1798
+ // But at runtime, tagged errors are preserved
1799
+ if (err) {
1800
+ assert.equal(err._tag, 'DatabaseError')
1801
+ }
1802
+ })
1803
+
1804
+ test('errorClass wraps all errors to specified type', () => {
1805
+ const DatabaseError = taggedError('DatabaseError')
1806
+
1807
+ const [err, value] = goTryRaw(() => 'test', { errorClass: DatabaseError })
1808
+
1809
+ attest<InstanceType<typeof DatabaseError> | undefined>(err)
1810
+ attest<string | undefined>(value)
1811
+ })
1812
+
1813
+ test('no options defaults to Error type (backward compatible)', () => {
1814
+ const [err, value] = goTryRaw(() => 'test')
1815
+
1816
+ attest<Error | undefined>(err)
1817
+ attest<string | undefined>(value)
1818
+ })
1819
+
1820
+ test('empty options object defaults to UnknownError type', () => {
1821
+ const [err, value] = goTryRaw(() => 'test', {})
1822
+
1823
+ attest<InstanceType<typeof UnknownError> | undefined>(err)
1824
+ attest<string | undefined>(value)
1825
+ })
1826
+ })
package/src/index.ts CHANGED
@@ -9,6 +9,8 @@ export type {
9
9
  GoTryAllOptions,
10
10
  ErrorConstructor,
11
11
  TaggedUnion,
12
+ GoTryRawOptions,
13
+ GoTryAllRawOptions,
12
14
  } from './types.js'
13
15
 
14
16
  // Export core functions
@@ -21,3 +23,6 @@ export { goTryAll, goTryAllRaw } from './goTryAll.js'
21
23
  export { taggedError } from './tagged-error.js'
22
24
  export { assert } from './assert.js'
23
25
  export { isSuccess, isFailure, success, failure, assertNever } from './result-helpers.js'
26
+
27
+ // Export UnknownError tagged error
28
+ export { UnknownError } from './unknown-error.js'
package/src/types.ts CHANGED
@@ -33,6 +33,30 @@ export interface GoTryAllOptions {
33
33
  */
34
34
  export type ErrorConstructor<E> = new (message: string, options?: { cause?: unknown }) => E
35
35
 
36
+ /**
37
+ * Checks if a value is a tagged error (has a _tag property).
38
+ */
39
+ export type IsTaggedError<T> = T extends { _tag: string } ? true : false
40
+
41
+ /**
42
+ * Options for goTryRaw function.
43
+ * errorClass and systemErrorClass are mutually exclusive - you can only provide one.
44
+ */
45
+ export type GoTryRawOptions<E = Error> =
46
+ | { errorClass: ErrorConstructor<E>; systemErrorClass?: never }
47
+ | { errorClass?: never; systemErrorClass: ErrorConstructor<E> }
48
+ | { errorClass?: never; systemErrorClass?: never }
49
+
50
+ /**
51
+ * Options for goTryAllRaw function.
52
+ * Includes concurrency control and error class options.
53
+ * errorClass and systemErrorClass are mutually exclusive.
54
+ */
55
+ export type GoTryAllRawOptions<E = Error> =
56
+ | { concurrency?: number; errorClass: ErrorConstructor<E>; systemErrorClass?: never }
57
+ | { concurrency?: number; errorClass?: never; systemErrorClass: ErrorConstructor<E> }
58
+ | { concurrency?: number; errorClass?: never; systemErrorClass?: never }
59
+
36
60
  /**
37
61
  * Creates a union type from multiple tagged error classes.
38
62
  *
@@ -0,0 +1,20 @@
1
+ import { taggedError } from './tagged-error.js'
2
+
3
+ /**
4
+ * Default system error class for errors that aren't already wrapped in a tagged error class.
5
+ *
6
+ * @example
7
+ * // By default, goTryRaw wraps unknown errors in UnknownError
8
+ * const [err, result] = goTryRaw(() => mightThrow())
9
+ * if (err) {
10
+ * console.log(err._tag) // 'UnknownError'
11
+ * }
12
+ *
13
+ * @example
14
+ * // Use a custom system error class
15
+ * const SystemError = taggedError('SystemError')
16
+ * const [err, result] = goTryRaw(() => mightThrow(), {
17
+ * systemErrorClass: SystemError
18
+ * })
19
+ */
20
+ export const UnknownError = taggedError('UnknownError')