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/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 {GoTryAllOptions} options - Optional configuration
113
- * @returns {Promise<[{ [K in keyof T]: Error | undefined }, { [K in keyof T]: T[K] | undefined }]>}
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?: GoTryAllOptions,
120
- ): Promise<[{ [K in keyof T]: Error | undefined }, { [K in keyof T]: T[K] | undefined }]> {
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]: Error | undefined }
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 (Error | undefined)[])[i] = undefined
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 (Error | undefined)[])[i] = isError(reason)
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('returns Error objects for failed promises', async () => {
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('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 () => {
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
  *