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/AGENTS.md +81 -23
- package/README.md +0 -16
- package/dist/index.cjs +82 -59
- package/dist/index.d.cts +176 -115
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +176 -115
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +81 -60
- package/package.json +1 -1
- package/src/assert.ts +59 -0
- package/src/goTry.ts +45 -0
- package/src/goTryAll.ts +141 -0
- package/src/goTryOr.ts +55 -0
- package/src/goTryRaw.ts +89 -0
- package/src/index.test.ts +188 -21
- package/src/index.ts +23 -474
- package/src/internals.ts +45 -0
- package/src/result-helpers.ts +46 -0
- package/src/tagged-error.ts +38 -0
- package/src/types.ts +50 -0
package/src/index.ts
CHANGED
|
@@ -1,474 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
export type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* const NetworkError = taggedError('NetworkError')
|
|
25
|
-
*
|
|
26
|
-
* type MyError = InstanceType<typeof DatabaseError> | InstanceType<typeof NetworkError>
|
|
27
|
-
*
|
|
28
|
-
* function fetchUser(id: string): Result<MyError, User> {
|
|
29
|
-
* const [err, user] = goTryRaw(fetch(`/users/${id}`), DatabaseError)
|
|
30
|
-
* if (err) return failure(err)
|
|
31
|
-
* // ...
|
|
32
|
-
* }
|
|
33
|
-
*
|
|
34
|
-
* // Pattern matching on errors
|
|
35
|
-
* if (err._tag === 'DatabaseError') {
|
|
36
|
-
* // TypeScript knows this is DatabaseError
|
|
37
|
-
* }
|
|
38
|
-
*/
|
|
39
|
-
export function taggedError<T extends string>(tag: T) {
|
|
40
|
-
return class TaggedErrorClass extends Error implements TaggedError<T> {
|
|
41
|
-
readonly _tag: T = tag
|
|
42
|
-
readonly cause?: unknown
|
|
43
|
-
|
|
44
|
-
constructor(message: string, options?: { cause?: unknown }) {
|
|
45
|
-
super(message)
|
|
46
|
-
this.name = tag
|
|
47
|
-
this.cause = options?.cause
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export type ResultWithDefault<E, T> = readonly [E | undefined, T]
|
|
53
|
-
|
|
54
|
-
export type MaybePromise<T> = T | Promise<T>
|
|
55
|
-
|
|
56
|
-
export interface GoTryAllOptions {
|
|
57
|
-
/**
|
|
58
|
-
* Maximum number of concurrent promises.
|
|
59
|
-
* Set to 0 (default) for unlimited concurrency (all promises run in parallel).
|
|
60
|
-
*/
|
|
61
|
-
concurrency?: number
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function isSuccess<E, T>(result: Result<E, T>): result is Success<T> {
|
|
65
|
-
return result[0] === undefined
|
|
66
|
-
}
|
|
67
|
-
export function isFailure<E, T>(result: Result<E, T>): result is Failure<E> {
|
|
68
|
-
return result[0] !== undefined
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function success<T>(value: T): Success<T> {
|
|
72
|
-
return [undefined, value] as const
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function failure<E>(error: E): Failure<E> {
|
|
76
|
-
return [error, undefined] as const
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function resolveDefault<T>(defaultValue: T | (() => T)): T {
|
|
80
|
-
return typeof defaultValue === 'function' ? (defaultValue as () => T)() : defaultValue
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Executes a function, promise, or value and returns a Result type with a fallback default.
|
|
85
|
-
* If an error occurs, it returns the error message and the default value.
|
|
86
|
-
*
|
|
87
|
-
* @template T The type of the successful result
|
|
88
|
-
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
|
|
89
|
-
* @param {T | (() => T)} defaultValue - The default value or a function to compute it (only called on failure)
|
|
90
|
-
* @returns {ResultWithDefault<string, T> | Promise<ResultWithDefault<string, T>>} A tuple of [error, value] or Promise thereof
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* // With a static default
|
|
94
|
-
* const [err, config] = goTryOr(() => JSON.parse('invalid'), { port: 3000 })
|
|
95
|
-
* // err is the error message, config is { port: 3000 }
|
|
96
|
-
*
|
|
97
|
-
* @example
|
|
98
|
-
* // With a computed default (lazy evaluation)
|
|
99
|
-
* const [err, user] = await goTryOr(fetchUser(id), () => ({
|
|
100
|
-
* id: 'anonymous',
|
|
101
|
-
* name: 'Guest'
|
|
102
|
-
* }))
|
|
103
|
-
*/
|
|
104
|
-
export function goTryOr<T>(fn: () => never, defaultValue: T | (() => T)): ResultWithDefault<string, T>
|
|
105
|
-
export function goTryOr<T>(
|
|
106
|
-
fn: () => Promise<T>,
|
|
107
|
-
defaultValue: T | (() => T),
|
|
108
|
-
): Promise<ResultWithDefault<string, T>>
|
|
109
|
-
export function goTryOr<T>(
|
|
110
|
-
promise: Promise<T>,
|
|
111
|
-
defaultValue: T | (() => T),
|
|
112
|
-
): Promise<ResultWithDefault<string, T>>
|
|
113
|
-
export function goTryOr<T>(fn: () => T, defaultValue: T | (() => T)): ResultWithDefault<string, T>
|
|
114
|
-
export function goTryOr<T>(value: T, defaultValue: T | (() => T)): ResultWithDefault<string, T>
|
|
115
|
-
export function goTryOr<T>(
|
|
116
|
-
value: T | Promise<T> | (() => T | Promise<T>),
|
|
117
|
-
defaultValue: T | (() => T),
|
|
118
|
-
): ResultWithDefault<string, T> | Promise<ResultWithDefault<string, T>> {
|
|
119
|
-
try {
|
|
120
|
-
const result =
|
|
121
|
-
typeof value === 'function' ? (value as () => T | Promise<T>)() : value
|
|
122
|
-
|
|
123
|
-
if (isPromise<T>(result)) {
|
|
124
|
-
return result
|
|
125
|
-
.then((resolvedValue) => success<T>(resolvedValue))
|
|
126
|
-
.catch((err) => [getErrorMessage(err), resolveDefault(defaultValue)] as const)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return success<T>(result)
|
|
130
|
-
} catch (err) {
|
|
131
|
-
return [getErrorMessage(err), resolveDefault(defaultValue)] as const
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
type PromiseFactory<T> = () => Promise<T>
|
|
136
|
-
|
|
137
|
-
async function runWithConcurrency<T extends readonly unknown[]>(
|
|
138
|
-
items: { [K in keyof T]: Promise<T[K]> | PromiseFactory<T[K]> },
|
|
139
|
-
concurrency: number,
|
|
140
|
-
): Promise<PromiseSettledResult<T[number]>[]> {
|
|
141
|
-
if (items.length === 0) {
|
|
142
|
-
return []
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Auto-detect factory mode by checking if first item is a function
|
|
146
|
-
const isFactoryMode = typeof items[0] === 'function'
|
|
147
|
-
|
|
148
|
-
// concurrency of 0 means unlimited (run all in parallel)
|
|
149
|
-
if (!isFactoryMode && (concurrency <= 0)) {
|
|
150
|
-
return Promise.allSettled(items as Promise<T[number]>[])
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const results: PromiseSettledResult<T[number]>[] = new Array(items.length)
|
|
154
|
-
let index = 0
|
|
155
|
-
|
|
156
|
-
async function worker(): Promise<void> {
|
|
157
|
-
while (index < items.length) {
|
|
158
|
-
const currentIndex = index++
|
|
159
|
-
try {
|
|
160
|
-
const item = items[currentIndex]
|
|
161
|
-
// If factory mode, call the function; otherwise await the promise directly
|
|
162
|
-
const value = isFactoryMode
|
|
163
|
-
? await (item as PromiseFactory<T[number]>)()
|
|
164
|
-
: await (item as Promise<T[number]>)
|
|
165
|
-
results[currentIndex] = { status: 'fulfilled', value }
|
|
166
|
-
} catch (reason) {
|
|
167
|
-
results[currentIndex] = { status: 'rejected', reason }
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Determine number of workers
|
|
173
|
-
const workerCount = concurrency <= 0 ? items.length : Math.min(concurrency, items.length)
|
|
174
|
-
|
|
175
|
-
// Start workers
|
|
176
|
-
const workers: Promise<void>[] = []
|
|
177
|
-
for (let i = 0; i < workerCount; i++) {
|
|
178
|
-
workers.push(worker())
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
await Promise.all(workers)
|
|
182
|
-
return results
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Executes multiple promises or factory functions in parallel (or with limited concurrency)
|
|
187
|
-
* and returns a tuple of [errors, results]. Unlike Promise.all, this doesn't fail fast -
|
|
188
|
-
* it waits for all promises to settle.
|
|
189
|
-
*
|
|
190
|
-
* Accepts either:
|
|
191
|
-
* - An array of promises (for simple parallel execution)
|
|
192
|
-
* - An array of factory functions that return promises (for lazy execution with concurrency control)
|
|
193
|
-
*
|
|
194
|
-
* @template T The tuple type of all promise results
|
|
195
|
-
* @param {readonly [...{ [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) }]} items - Array of promises or factories
|
|
196
|
-
* @param {GoTryAllOptions} options - Optional configuration
|
|
197
|
-
* @returns {Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]>}
|
|
198
|
-
* A tuple where the first element is a tuple of errors (or undefined) and
|
|
199
|
-
* the second element is a tuple of results (or undefined), preserving input order
|
|
200
|
-
*
|
|
201
|
-
* @example
|
|
202
|
-
* // Run all in parallel (default) - with promises
|
|
203
|
-
* const [errors, results] = await goTryAll([
|
|
204
|
-
* fetchUser(1),
|
|
205
|
-
* fetchUser(2),
|
|
206
|
-
* fetchUser(3)
|
|
207
|
-
* ])
|
|
208
|
-
*
|
|
209
|
-
* @example
|
|
210
|
-
* // Limit concurrency with factory functions (lazy execution)
|
|
211
|
-
* const [errors, results] = await goTryAll([
|
|
212
|
-
* () => fetchUser(1), // Only called when a slot is available
|
|
213
|
-
* () => fetchUser(2), // Only called when a slot is available
|
|
214
|
-
* () => fetchUser(3), // Only called when a slot is available
|
|
215
|
-
* ], { concurrency: 2 })
|
|
216
|
-
*/
|
|
217
|
-
export async function goTryAll<T extends readonly unknown[]>(
|
|
218
|
-
items: { [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) },
|
|
219
|
-
options?: GoTryAllOptions,
|
|
220
|
-
): Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]> {
|
|
221
|
-
const settled = await runWithConcurrency(items, options?.concurrency ?? 0)
|
|
222
|
-
|
|
223
|
-
const errors = [] as { [K in keyof T]: string | undefined }
|
|
224
|
-
const results = [] as { [K in keyof T]: T[K] | undefined }
|
|
225
|
-
|
|
226
|
-
for (let i = 0; i < settled.length; i++) {
|
|
227
|
-
const item = settled[i]!
|
|
228
|
-
if (item.status === 'fulfilled') {
|
|
229
|
-
;(errors as (string | undefined)[])[i] = undefined
|
|
230
|
-
;(results as unknown[])[i] = (item as PromiseFulfilledResult<T[number]>).value
|
|
231
|
-
} else {
|
|
232
|
-
;(errors as (string | undefined)[])[i] = getErrorMessage((item as PromiseRejectedResult).reason)
|
|
233
|
-
;(results as unknown[])[i] = undefined
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return [errors, results]
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Like `goTryAll`, but returns raw Error objects instead of error messages.
|
|
242
|
-
*
|
|
243
|
-
* @template T The tuple type of all promise results
|
|
244
|
-
* @param {readonly [...{ [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) }]} items - Array of promises or factories
|
|
245
|
-
* @param {GoTryAllOptions} options - Optional configuration
|
|
246
|
-
* @returns {Promise<[{ [K in keyof T]: Error | undefined }, { [K in keyof T]: T[K] | undefined }]>}
|
|
247
|
-
* A tuple where the first element is a tuple of Error objects (or undefined) and
|
|
248
|
-
* the second element is a tuple of results (or undefined), preserving input order
|
|
249
|
-
*/
|
|
250
|
-
export async function goTryAllRaw<T extends readonly unknown[]>(
|
|
251
|
-
items: { [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) },
|
|
252
|
-
options?: GoTryAllOptions,
|
|
253
|
-
): Promise<[{ [K in keyof T]: Error | undefined }, { [K in keyof T]: T[K] | undefined }]> {
|
|
254
|
-
const settled = await runWithConcurrency(items, options?.concurrency ?? 0)
|
|
255
|
-
|
|
256
|
-
const errors = [] as { [K in keyof T]: Error | undefined }
|
|
257
|
-
const results = [] as { [K in keyof T]: T[K] | undefined }
|
|
258
|
-
|
|
259
|
-
for (let i = 0; i < settled.length; i++) {
|
|
260
|
-
const item = settled[i]!
|
|
261
|
-
if (item.status === 'fulfilled') {
|
|
262
|
-
;(errors as (Error | undefined)[])[i] = undefined
|
|
263
|
-
;(results as unknown[])[i] = (item as PromiseFulfilledResult<T[number]>).value
|
|
264
|
-
} else {
|
|
265
|
-
const reason = (item as PromiseRejectedResult).reason
|
|
266
|
-
;(errors as (Error | undefined)[])[i] = isError(reason)
|
|
267
|
-
? reason
|
|
268
|
-
: new Error(String(reason))
|
|
269
|
-
;(results as unknown[])[i] = undefined
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return [errors, results]
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function getErrorMessage(error: unknown): string {
|
|
277
|
-
if (error === undefined) return 'undefined'
|
|
278
|
-
|
|
279
|
-
if (typeof error === 'string') return error
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
typeof error === 'object' &&
|
|
283
|
-
error !== null &&
|
|
284
|
-
'message' in error &&
|
|
285
|
-
typeof (error as Record<string, unknown>).message === 'string'
|
|
286
|
-
) {
|
|
287
|
-
return (error as { message: string }).message
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
return JSON.stringify(error)
|
|
292
|
-
} catch {
|
|
293
|
-
// fallback in case there's an error stringifying the error
|
|
294
|
-
// with circular references for example.
|
|
295
|
-
return String(error)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function isPromise<T>(value: unknown): value is Promise<T> {
|
|
300
|
-
return (
|
|
301
|
-
typeof value === 'object' &&
|
|
302
|
-
value !== null &&
|
|
303
|
-
'then' in value &&
|
|
304
|
-
typeof (value as { then: unknown }).then === 'function'
|
|
305
|
-
)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function isError(value: unknown): value is Error {
|
|
309
|
-
return value instanceof Error
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Executes a function, promise, or value and returns a Result type.
|
|
314
|
-
* If an error occurs, it returns a Failure with the error message as a string.
|
|
315
|
-
*
|
|
316
|
-
* @template T The type of the successful result
|
|
317
|
-
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
|
|
318
|
-
* @returns {Result<string, T> | Promise<Result<string, T>>} A Result type or a Promise of a Result type
|
|
319
|
-
*
|
|
320
|
-
* @example
|
|
321
|
-
* // With a value
|
|
322
|
-
* const [err, result] = goTry(42);
|
|
323
|
-
*
|
|
324
|
-
* @example
|
|
325
|
-
* // With a function
|
|
326
|
-
* const [err, result] = goTry(() => JSON.parse('{"key": "value"}'));
|
|
327
|
-
*
|
|
328
|
-
* @example
|
|
329
|
-
* // With a promise
|
|
330
|
-
* const [err, result] = await goTry(fetch('https://api.example.com/data'));
|
|
331
|
-
*/
|
|
332
|
-
export function goTry<T>(fn: () => never): Result<string, never>
|
|
333
|
-
export function goTry<T>(fn: () => Promise<T>): Promise<Result<string, T>>
|
|
334
|
-
export function goTry<T>(promise: Promise<T>): Promise<Result<string, T>>
|
|
335
|
-
export function goTry<T>(fn: () => T): Result<string, T>
|
|
336
|
-
export function goTry<T>(value: T): Result<string, T>
|
|
337
|
-
export function goTry<T>(
|
|
338
|
-
value: T | Promise<T> | (() => T | Promise<T>),
|
|
339
|
-
): Result<string, T> | Promise<Result<string, T>> {
|
|
340
|
-
try {
|
|
341
|
-
const result =
|
|
342
|
-
typeof value === 'function' ? (value as () => T | Promise<T>)() : value
|
|
343
|
-
if (isPromise<T>(result)) {
|
|
344
|
-
return result
|
|
345
|
-
.then((resolvedValue) => success<T>(resolvedValue))
|
|
346
|
-
.catch((err) => failure<string>(getErrorMessage(err)))
|
|
347
|
-
}
|
|
348
|
-
return success<T>(result)
|
|
349
|
-
} catch (err) {
|
|
350
|
-
return failure<string>(getErrorMessage(err))
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Type for error constructors that can be used with goTryRaw.
|
|
356
|
-
*/
|
|
357
|
-
export type ErrorConstructor<E> = new (message: string, options?: { cause?: unknown }) => E
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Extracts the instance type from a tagged error class.
|
|
361
|
-
* Useful for creating cleaner error type definitions.
|
|
362
|
-
*
|
|
363
|
-
* @template T The tagged error class type
|
|
364
|
-
* @returns The instance type of the error class
|
|
365
|
-
*
|
|
366
|
-
* @example
|
|
367
|
-
* const DatabaseError = taggedError('DatabaseError')
|
|
368
|
-
* type DbError = TaggedInstance<typeof DatabaseError>
|
|
369
|
-
* // Equivalent to: InstanceType<typeof DatabaseError>
|
|
370
|
-
*/
|
|
371
|
-
export type TaggedInstance<T extends ErrorConstructor<unknown>> =
|
|
372
|
-
T extends ErrorConstructor<infer E> ? E : never
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Creates a union type from multiple tagged error classes.
|
|
376
|
-
*
|
|
377
|
-
* @template T A tuple of tagged error class types
|
|
378
|
-
* @returns A union of all instance types
|
|
379
|
-
*
|
|
380
|
-
* @example
|
|
381
|
-
* const DatabaseError = taggedError('DatabaseError')
|
|
382
|
-
* const NetworkError = taggedError('NetworkError')
|
|
383
|
-
*
|
|
384
|
-
* type AppError = TaggedUnion<[typeof DatabaseError, typeof NetworkError]>
|
|
385
|
-
* // Equivalent to: DatabaseError | NetworkError
|
|
386
|
-
*/
|
|
387
|
-
export type TaggedUnion<T extends readonly ErrorConstructor<unknown>[]> =
|
|
388
|
-
{ [K in keyof T]: T[K] extends ErrorConstructor<infer E> ? E : never }[number]
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Executes a function, promise, or value and returns a Result type.
|
|
392
|
-
* If an error occurs, it returns a Failure with the raw error object.
|
|
393
|
-
*
|
|
394
|
-
* @template T The type of the successful result
|
|
395
|
-
* @template E The type of the error, defaults to Error
|
|
396
|
-
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
|
|
397
|
-
* @param {ErrorConstructor<E>} [ErrorClass] - Optional error constructor to wrap caught errors
|
|
398
|
-
* @returns {Result<E, T> | Promise<Result<E, T>>} A Result type or a Promise of a Result type
|
|
399
|
-
*
|
|
400
|
-
* @example
|
|
401
|
-
* // With a value
|
|
402
|
-
* const [err, result] = goTryRaw(42);
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* // With a function
|
|
406
|
-
* const [err, result] = goTryRaw(() => JSON.parse('{"key": "value"}'));
|
|
407
|
-
*
|
|
408
|
-
* @example
|
|
409
|
-
* // With a promise
|
|
410
|
-
* const [err, result] = await goTryRaw(fetch('https://api.example.com/data'));
|
|
411
|
-
*
|
|
412
|
-
* @example
|
|
413
|
-
* // With tagged error for discriminated unions
|
|
414
|
-
* const DatabaseError = taggedError('DatabaseError');
|
|
415
|
-
* const [err, result] = await goTryRaw(fetchData(), DatabaseError);
|
|
416
|
-
* // err is InstanceType<typeof DatabaseError> | undefined
|
|
417
|
-
*/
|
|
418
|
-
export function goTryRaw<T, E = Error>(fn: () => never): Result<E, never>
|
|
419
|
-
export function goTryRaw<T, E = Error>(fn: () => never, ErrorClass: ErrorConstructor<E>): Result<E, never>
|
|
420
|
-
export function goTryRaw<T, E = Error>(
|
|
421
|
-
fn: () => Promise<T>,
|
|
422
|
-
): Promise<Result<E, T>>
|
|
423
|
-
export function goTryRaw<T, E = Error>(
|
|
424
|
-
fn: () => Promise<T>,
|
|
425
|
-
ErrorClass: ErrorConstructor<E>,
|
|
426
|
-
): Promise<Result<E, T>>
|
|
427
|
-
export function goTryRaw<T, E = Error>(
|
|
428
|
-
promise: Promise<T>,
|
|
429
|
-
): Promise<Result<E, T>>
|
|
430
|
-
export function goTryRaw<T, E = Error>(
|
|
431
|
-
promise: Promise<T>,
|
|
432
|
-
ErrorClass: ErrorConstructor<E>,
|
|
433
|
-
): Promise<Result<E, T>>
|
|
434
|
-
export function goTryRaw<T, E = Error>(fn: () => T): Result<E, T>
|
|
435
|
-
export function goTryRaw<T, E = Error>(fn: () => T, ErrorClass: ErrorConstructor<E>): Result<E, T>
|
|
436
|
-
export function goTryRaw<T, E = Error>(value: T): Result<E, T>
|
|
437
|
-
export function goTryRaw<T, E = Error>(value: T, ErrorClass: ErrorConstructor<E>): Result<E, T>
|
|
438
|
-
export function goTryRaw<T, E = Error>(
|
|
439
|
-
value: T | Promise<T> | (() => T | Promise<T>),
|
|
440
|
-
ErrorClass?: ErrorConstructor<E>,
|
|
441
|
-
): Result<E, T> | Promise<Result<E, T>> {
|
|
442
|
-
// Helper to wrap error in the provided class or return as-is
|
|
443
|
-
const wrapError = (err: unknown): E => {
|
|
444
|
-
if (ErrorClass) {
|
|
445
|
-
if (err === undefined) {
|
|
446
|
-
return new ErrorClass('undefined')
|
|
447
|
-
}
|
|
448
|
-
if (isError(err)) {
|
|
449
|
-
return new ErrorClass(err.message, { cause: err })
|
|
450
|
-
}
|
|
451
|
-
return new ErrorClass(String(err))
|
|
452
|
-
}
|
|
453
|
-
// Default behavior: return raw error
|
|
454
|
-
if (err === undefined) {
|
|
455
|
-
return new Error('undefined') as unknown as E
|
|
456
|
-
}
|
|
457
|
-
return (
|
|
458
|
-
isError(err) ? err : new Error(String(err))
|
|
459
|
-
) as unknown as E
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
try {
|
|
463
|
-
const result =
|
|
464
|
-
typeof value === 'function' ? (value as () => T | Promise<T>)() : value
|
|
465
|
-
if (isPromise<T>(result)) {
|
|
466
|
-
return result
|
|
467
|
-
.then((resolvedValue) => success<T>(resolvedValue))
|
|
468
|
-
.catch((err) => failure<E>(wrapError(err)))
|
|
469
|
-
}
|
|
470
|
-
return success<T>(result)
|
|
471
|
-
} catch (err) {
|
|
472
|
-
return failure<E>(wrapError(err))
|
|
473
|
-
}
|
|
474
|
-
}
|
|
1
|
+
// Export all types
|
|
2
|
+
export type {
|
|
3
|
+
Success,
|
|
4
|
+
Failure,
|
|
5
|
+
Result,
|
|
6
|
+
TaggedError,
|
|
7
|
+
ResultWithDefault,
|
|
8
|
+
MaybePromise,
|
|
9
|
+
GoTryAllOptions,
|
|
10
|
+
ErrorConstructor,
|
|
11
|
+
TaggedUnion,
|
|
12
|
+
} from './types.js'
|
|
13
|
+
|
|
14
|
+
// Export core functions
|
|
15
|
+
export { goTry } from './goTry.js'
|
|
16
|
+
export { goTryRaw } from './goTryRaw.js'
|
|
17
|
+
export { goTryOr } from './goTryOr.js'
|
|
18
|
+
export { goTryAll, goTryAllRaw } from './goTryAll.js'
|
|
19
|
+
|
|
20
|
+
// Export helper functions
|
|
21
|
+
export { taggedError } from './tagged-error.js'
|
|
22
|
+
export { assert } from './assert.js'
|
|
23
|
+
export { isSuccess, isFailure, success, failure, assertNever } from './result-helpers.js'
|
package/src/internals.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal utility functions (not exported from index)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type PromiseFactory<T> = () => Promise<T>
|
|
6
|
+
|
|
7
|
+
export function getErrorMessage(error: unknown): string {
|
|
8
|
+
if (error === undefined) return 'undefined'
|
|
9
|
+
|
|
10
|
+
if (typeof error === 'string') return error
|
|
11
|
+
|
|
12
|
+
if (
|
|
13
|
+
typeof error === 'object' &&
|
|
14
|
+
error !== null &&
|
|
15
|
+
'message' in error &&
|
|
16
|
+
typeof (error as Record<string, unknown>).message === 'string'
|
|
17
|
+
) {
|
|
18
|
+
return (error as { message: string }).message
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(error)
|
|
23
|
+
} catch {
|
|
24
|
+
// fallback in case there's an error stringifying the error
|
|
25
|
+
// with circular references for example.
|
|
26
|
+
return String(error)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isPromise<T>(value: unknown): value is Promise<T> {
|
|
31
|
+
return (
|
|
32
|
+
typeof value === 'object' &&
|
|
33
|
+
value !== null &&
|
|
34
|
+
'then' in value &&
|
|
35
|
+
typeof (value as { then: unknown }).then === 'function'
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isError(value: unknown): value is Error {
|
|
40
|
+
return value instanceof Error
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveDefault<T>(defaultValue: T | (() => T)): T {
|
|
44
|
+
return typeof defaultValue === 'function' ? (defaultValue as () => T)() : defaultValue
|
|
45
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Result, Success, Failure } from './types.js'
|
|
2
|
+
|
|
3
|
+
export function isSuccess<E, T>(result: Result<E, T>): result is Success<T> {
|
|
4
|
+
return result[0] === undefined
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isFailure<E, T>(result: Result<E, T>): result is Failure<E> {
|
|
8
|
+
return result[0] !== undefined
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function success<T>(value: T): Success<T> {
|
|
12
|
+
return [undefined, value] as const
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function failure<E>(error: E): Failure<E> {
|
|
16
|
+
return [error, undefined] as const
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Helper for exhaustive switch checks on discriminated unions.
|
|
21
|
+
* If this function is called, it means a case was forgotten in a switch statement.
|
|
22
|
+
* Use this in the `default` case of switch statements handling tagged errors.
|
|
23
|
+
*
|
|
24
|
+
* @param value - The value that should be of type `never` if all cases are handled
|
|
25
|
+
* @throws {Error} Always throws an error indicating unhandled case
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const DatabaseError = taggedError('DatabaseError')
|
|
29
|
+
* const NetworkError = taggedError('NetworkError')
|
|
30
|
+
* type AppError = InstanceType<typeof DatabaseError> | InstanceType<typeof NetworkError>
|
|
31
|
+
*
|
|
32
|
+
* function handleError(err: AppError): string {
|
|
33
|
+
* switch (err._tag) {
|
|
34
|
+
* case 'DatabaseError':
|
|
35
|
+
* return `DB: ${err.message}`
|
|
36
|
+
* case 'NetworkError':
|
|
37
|
+
* return `NET: ${err.message}`
|
|
38
|
+
* default:
|
|
39
|
+
* // TypeScript will error if we forget a case above
|
|
40
|
+
* return assertNever(err)
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
*/
|
|
44
|
+
export function assertNever(value: never): never {
|
|
45
|
+
throw new Error(`Unhandled case: ${String(value)}`)
|
|
46
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TaggedError } from './types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a tagged error class for discriminated error handling.
|
|
5
|
+
*
|
|
6
|
+
* @template T The literal type of the tag
|
|
7
|
+
* @param tag The string tag to identify this error type (e.g., 'DatabaseError')
|
|
8
|
+
* @returns A class constructor for creating tagged errors
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const DatabaseError = taggedError('DatabaseError')
|
|
12
|
+
* const NetworkError = taggedError('NetworkError')
|
|
13
|
+
*
|
|
14
|
+
* type MyError = InstanceType<typeof DatabaseError> | InstanceType<typeof NetworkError>
|
|
15
|
+
*
|
|
16
|
+
* function fetchUser(id: string): Result<MyError, User> {
|
|
17
|
+
* const [err, user] = goTryRaw(fetch(`/users/${id}`), DatabaseError)
|
|
18
|
+
* if (err) return failure(err)
|
|
19
|
+
* // ...
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* // Pattern matching on errors
|
|
23
|
+
* if (err._tag === 'DatabaseError') {
|
|
24
|
+
* // TypeScript knows this is DatabaseError
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
export function taggedError<T extends string>(tag: T) {
|
|
28
|
+
return class TaggedErrorClass extends Error implements TaggedError<T> {
|
|
29
|
+
readonly _tag: T = tag
|
|
30
|
+
readonly cause?: unknown
|
|
31
|
+
|
|
32
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
33
|
+
super(message)
|
|
34
|
+
this.name = tag
|
|
35
|
+
this.cause = options?.cause
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Result types for go-go-try
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type Success<T> = readonly [undefined, T]
|
|
6
|
+
export type Failure<E> = readonly [E, undefined]
|
|
7
|
+
export type Result<E, T> = Success<T> | Failure<E>
|
|
8
|
+
|
|
9
|
+
export type ResultWithDefault<E, T> = readonly [E | undefined, T]
|
|
10
|
+
|
|
11
|
+
export type MaybePromise<T> = T | Promise<T>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base interface for tagged errors.
|
|
15
|
+
* The `_tag` property enables discriminated union narrowing.
|
|
16
|
+
*/
|
|
17
|
+
export interface TaggedError<T extends string> {
|
|
18
|
+
readonly _tag: T
|
|
19
|
+
readonly message: string
|
|
20
|
+
readonly cause?: unknown
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface GoTryAllOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Maximum number of concurrent promises.
|
|
26
|
+
* Set to 0 (default) for unlimited concurrency (all promises run in parallel).
|
|
27
|
+
*/
|
|
28
|
+
concurrency?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Type for error constructors that can be used with goTryRaw.
|
|
33
|
+
*/
|
|
34
|
+
export type ErrorConstructor<E> = new (message: string, options?: { cause?: unknown }) => E
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a union type from multiple tagged error classes.
|
|
38
|
+
*
|
|
39
|
+
* @template T A tuple of tagged error class types
|
|
40
|
+
* @returns A union of all instance types
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const DatabaseError = taggedError('DatabaseError')
|
|
44
|
+
* const NetworkError = taggedError('NetworkError')
|
|
45
|
+
*
|
|
46
|
+
* type AppError = TaggedUnion<[typeof DatabaseError, typeof NetworkError]>
|
|
47
|
+
* // Equivalent to: DatabaseError | NetworkError
|
|
48
|
+
*/
|
|
49
|
+
export type TaggedUnion<T extends readonly ErrorConstructor<unknown>[]> =
|
|
50
|
+
{ [K in keyof T]: T[K] extends ErrorConstructor<infer E> ? E : never }[number]
|