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/README.md +129 -22
- package/dist/index.cjs +63 -17
- package/dist/index.d.cts +96 -22
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +96 -22
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +63 -18
- package/package.json +3 -3
- package/src/goTryAll.ts +55 -11
- package/src/goTryRaw.ts +64 -27
- package/src/index.test.ts +320 -26
- package/src/index.ts +5 -0
- package/src/types.ts +24 -0
- package/src/unknown-error.ts +20 -0
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
|
-
|
|
323
|
-
assert.equal(
|
|
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('
|
|
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('
|
|
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')
|