go-go-try 7.4.0 → 8.0.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 +45 -4
- package/dist/index.cjs +95 -7
- package/dist/index.d.cts +118 -18
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +118 -18
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +92 -8
- package/package.json +1 -1
- package/src/ensure.test.ts +116 -0
- package/src/ensure.ts +137 -0
- package/src/go.test.ts +107 -0
- package/src/go.ts +37 -0
- package/src/goElse.ts +74 -0
- package/src/goTryAll.ts +55 -11
- package/src/index.test.ts +87 -2
- package/src/index.ts +5 -0
- package/src/types.ts +10 -0
package/src/goTryAll.ts
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
|
-
import type { GoTryAllOptions } from './types.js'
|
|
1
|
+
import type { GoTryAllOptions, GoTryAllRawOptions, ErrorConstructor } from './types.js'
|
|
2
2
|
import { isError, getErrorMessage, type PromiseFactory } from './internals.js'
|
|
3
|
+
import { UnknownError } from './unknown-error.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a value is a tagged error (has a _tag property).
|
|
7
|
+
*/
|
|
8
|
+
function isTaggedError(err: unknown): err is { _tag: string } {
|
|
9
|
+
return isError(err) && '_tag' in err && typeof (err as { _tag?: unknown })._tag === 'string'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Wraps an error based on the provided options (errorClass/systemErrorClass).
|
|
14
|
+
* Consistent with goTryRaw behavior.
|
|
15
|
+
*/
|
|
16
|
+
function wrapError<E>(err: unknown, errorClass: ErrorConstructor<E> | undefined, systemErrorClass: ErrorConstructor<E> | undefined): E {
|
|
17
|
+
// If errorClass is specified, wrap all errors with it
|
|
18
|
+
if (errorClass) {
|
|
19
|
+
if (err === undefined) {
|
|
20
|
+
return new errorClass('undefined')
|
|
21
|
+
}
|
|
22
|
+
if (isError(err)) {
|
|
23
|
+
return new errorClass(err.message, { cause: err })
|
|
24
|
+
}
|
|
25
|
+
return new errorClass(String(err))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// If systemErrorClass is specified (or defaulted to UnknownError), only wrap non-tagged errors
|
|
29
|
+
const actualSystemErrorClass = systemErrorClass ?? UnknownError
|
|
30
|
+
if (isTaggedError(err)) {
|
|
31
|
+
return err as unknown as E
|
|
32
|
+
}
|
|
33
|
+
if (err === undefined) {
|
|
34
|
+
return new actualSystemErrorClass('undefined') as unknown as E
|
|
35
|
+
}
|
|
36
|
+
if (isError(err)) {
|
|
37
|
+
return new actualSystemErrorClass(err.message, { cause: err }) as unknown as E
|
|
38
|
+
}
|
|
39
|
+
return new actualSystemErrorClass(String(err)) as unknown as E
|
|
40
|
+
}
|
|
3
41
|
|
|
4
42
|
async function runWithConcurrency<T extends readonly unknown[]>(
|
|
5
43
|
items: { [K in keyof T]: Promise<T[K]> | PromiseFactory<T[K]> },
|
|
@@ -106,33 +144,39 @@ export async function goTryAll<T extends readonly unknown[]>(
|
|
|
106
144
|
|
|
107
145
|
/**
|
|
108
146
|
* Like `goTryAll`, but returns raw Error objects instead of error messages.
|
|
147
|
+
* Non-tagged errors are wrapped in `UnknownError` by default (consistent with `goTryRaw`).
|
|
148
|
+
* Tagged errors pass through unchanged.
|
|
149
|
+
*
|
|
150
|
+
* Supports `errorClass` and `systemErrorClass` options (mutually exclusive):
|
|
151
|
+
* - `errorClass`: Wrap ALL errors in the specified class
|
|
152
|
+
* - `systemErrorClass`: Only wrap non-tagged errors (defaults to UnknownError)
|
|
109
153
|
*
|
|
110
154
|
* @template T The tuple type of all promise results
|
|
155
|
+
* @template E The type of the error
|
|
111
156
|
* @param {readonly [...{ [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) }]} items - Array of promises or factories
|
|
112
|
-
* @param {
|
|
113
|
-
* @returns {Promise<[{ [K in keyof T]:
|
|
157
|
+
* @param {GoTryAllRawOptions<E>} options - Optional configuration
|
|
158
|
+
* @returns {Promise<[{ [K in keyof T]: E | undefined }, { [K in keyof T]: T[K] | undefined }]>}
|
|
114
159
|
* A tuple where the first element is a tuple of Error objects (or undefined) and
|
|
115
160
|
* the second element is a tuple of results (or undefined), preserving input order
|
|
116
161
|
*/
|
|
117
|
-
export async function goTryAllRaw<T extends readonly unknown[]
|
|
162
|
+
export async function goTryAllRaw<T extends readonly unknown[], E = InstanceType<typeof UnknownError>>(
|
|
118
163
|
items: { [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) },
|
|
119
|
-
options?:
|
|
120
|
-
): Promise<[{ [K in keyof T]:
|
|
164
|
+
options?: GoTryAllRawOptions<E>,
|
|
165
|
+
): Promise<[{ [K in keyof T]: E | undefined }, { [K in keyof T]: T[K] | undefined }]> {
|
|
166
|
+
const { errorClass, systemErrorClass } = options || {}
|
|
121
167
|
const settled = await runWithConcurrency(items, options?.concurrency ?? 0)
|
|
122
168
|
|
|
123
|
-
const errors = [] as { [K in keyof T]:
|
|
169
|
+
const errors = [] as { [K in keyof T]: E | undefined }
|
|
124
170
|
const results = [] as { [K in keyof T]: T[K] | undefined }
|
|
125
171
|
|
|
126
172
|
for (let i = 0; i < settled.length; i++) {
|
|
127
173
|
const item = settled[i]!
|
|
128
174
|
if (item.status === 'fulfilled') {
|
|
129
|
-
;(errors as (
|
|
175
|
+
;(errors as (E | undefined)[])[i] = undefined
|
|
130
176
|
;(results as unknown[])[i] = (item as PromiseFulfilledResult<T[number]>).value
|
|
131
177
|
} else {
|
|
132
178
|
const reason = (item as PromiseRejectedResult).reason
|
|
133
|
-
;(errors as (
|
|
134
|
-
? reason
|
|
135
|
-
: new Error(String(reason))
|
|
179
|
+
;(errors as (E | undefined)[])[i] = wrapError(reason, errorClass, systemErrorClass)
|
|
136
180
|
;(results as unknown[])[i] = undefined
|
|
137
181
|
}
|
|
138
182
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -1107,7 +1107,7 @@ describe('goTryAllRaw', () => {
|
|
|
1107
1107
|
assert.deepEqual(results, ['a', 42, true])
|
|
1108
1108
|
})
|
|
1109
1109
|
|
|
1110
|
-
test('
|
|
1110
|
+
test('wraps non-tagged errors in UnknownError', async () => {
|
|
1111
1111
|
const [errors, results] = await goTryAllRaw([
|
|
1112
1112
|
Promise.resolve('success'),
|
|
1113
1113
|
Promise.reject(new Error('fail1')),
|
|
@@ -1115,6 +1115,8 @@ describe('goTryAllRaw', () => {
|
|
|
1115
1115
|
])
|
|
1116
1116
|
|
|
1117
1117
|
assert.equal(errors[0], undefined)
|
|
1118
|
+
assert.ok(errors[1] instanceof UnknownError)
|
|
1119
|
+
assert.equal(errors[1]?._tag, 'UnknownError')
|
|
1118
1120
|
assert.equal(errors[1]?.message, 'fail1')
|
|
1119
1121
|
assert.equal(errors[2], undefined)
|
|
1120
1122
|
|
|
@@ -1123,15 +1125,44 @@ describe('goTryAllRaw', () => {
|
|
|
1123
1125
|
assert.equal(results[2], 42)
|
|
1124
1126
|
})
|
|
1125
1127
|
|
|
1126
|
-
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 () => {
|
|
1127
1152
|
const [errors] = await goTryAllRaw([
|
|
1128
1153
|
Promise.reject('string error'),
|
|
1129
1154
|
Promise.reject(42),
|
|
1130
1155
|
Promise.reject(undefined),
|
|
1131
1156
|
])
|
|
1132
1157
|
|
|
1158
|
+
assert.ok(errors[0] instanceof UnknownError)
|
|
1159
|
+
assert.equal(errors[0]?._tag, 'UnknownError')
|
|
1133
1160
|
assert.equal(errors[0]?.message, 'string error')
|
|
1161
|
+
assert.ok(errors[1] instanceof UnknownError)
|
|
1162
|
+
assert.equal(errors[1]?._tag, 'UnknownError')
|
|
1134
1163
|
assert.equal(errors[1]?.message, '42')
|
|
1164
|
+
assert.ok(errors[2] instanceof UnknownError)
|
|
1165
|
+
assert.equal(errors[2]?._tag, 'UnknownError')
|
|
1135
1166
|
assert.equal(errors[2]?.message, 'undefined')
|
|
1136
1167
|
})
|
|
1137
1168
|
|
|
@@ -1141,6 +1172,60 @@ describe('goTryAllRaw', () => {
|
|
|
1141
1172
|
assert.deepEqual(errors, [])
|
|
1142
1173
|
assert.deepEqual(results, [])
|
|
1143
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
|
+
})
|
|
1144
1229
|
})
|
|
1145
1230
|
|
|
1146
1231
|
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type {
|
|
|
10
10
|
ErrorConstructor,
|
|
11
11
|
TaggedUnion,
|
|
12
12
|
GoTryRawOptions,
|
|
13
|
+
GoTryAllRawOptions,
|
|
13
14
|
} from './types.js'
|
|
14
15
|
|
|
15
16
|
// Export core functions
|
|
@@ -21,7 +22,11 @@ export { goTryAll, goTryAllRaw } from './goTryAll.js'
|
|
|
21
22
|
// Export helper functions
|
|
22
23
|
export { taggedError } from './tagged-error.js'
|
|
23
24
|
export { assert } from './assert.js'
|
|
25
|
+
export { ensure } from './ensure.js'
|
|
24
26
|
export { isSuccess, isFailure, success, failure, assertNever } from './result-helpers.js'
|
|
25
27
|
|
|
26
28
|
// Export UnknownError tagged error
|
|
27
29
|
export { UnknownError } from './unknown-error.js'
|
|
30
|
+
|
|
31
|
+
// Export go aliases
|
|
32
|
+
export { go, goAll, goElse } from './go.js'
|
package/src/types.ts
CHANGED
|
@@ -47,6 +47,16 @@ export type GoTryRawOptions<E = Error> =
|
|
|
47
47
|
| { errorClass?: never; systemErrorClass: ErrorConstructor<E> }
|
|
48
48
|
| { errorClass?: never; systemErrorClass?: never }
|
|
49
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
|
+
|
|
50
60
|
/**
|
|
51
61
|
* Creates a union type from multiple tagged error classes.
|
|
52
62
|
*
|