go-go-try 7.2.1 → 7.4.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/assert.ts ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Asserts that a condition is true, otherwise throws the provided error.
3
+ * Provides type narrowing when used with Result types.
4
+ *
5
+ * @param condition - The condition to assert
6
+ * @param error - An Error instance or string message to throw if condition is falsy
7
+ * @throws {Error} Throws the provided error if condition is falsy
8
+ *
9
+ * @example
10
+ * // With Result type - narrows error to undefined after assertion
11
+ * const [err, user] = goTryRaw(fetchUser(), DatabaseError)
12
+ * assert(err === undefined, new DatabaseError('Failed to fetch user'))
13
+ * // TypeScript now knows: err is undefined, user is User
14
+ *
15
+ * @example
16
+ * // With string message
17
+ * assert(response.ok, 'Response was not ok')
18
+ *
19
+ * @example
20
+ * // With custom Error instance
21
+ * assert(value > 0, new ValidationError('Value must be positive'))
22
+ */
23
+ export function assert(condition: unknown, error: Error | string): asserts condition
24
+
25
+ /**
26
+ * Asserts that a condition is true, otherwise instantiates and throws the error class.
27
+ * Provides type narrowing when used with Result types.
28
+ *
29
+ * @param condition - The condition to assert
30
+ * @param ErrorClass - An Error class constructor (e.g., from taggedError)
31
+ * @param message - The error message to pass to the constructor
32
+ * @throws {Error} Throws a new instance of ErrorClass if condition is falsy
33
+ *
34
+ * @example
35
+ * const ValidationError = taggedError('ValidationError')
36
+ * assert(value > 0, ValidationError, 'Value must be positive')
37
+ * // Equivalent to: if (!(value > 0)) throw new ValidationError('Value must be positive')
38
+ */
39
+ export function assert<T extends Error>(
40
+ condition: unknown,
41
+ ErrorClass: new (message: string) => T,
42
+ message: string,
43
+ ): asserts condition
44
+
45
+ export function assert(
46
+ condition: unknown,
47
+ errorOrClass: Error | string | (new (message: string) => Error),
48
+ message?: string,
49
+ ): asserts condition {
50
+ if (!condition) {
51
+ if (typeof errorOrClass === 'string') {
52
+ throw new Error(errorOrClass)
53
+ }
54
+ if (typeof errorOrClass === 'function' && message !== undefined) {
55
+ throw new errorOrClass(message)
56
+ }
57
+ throw errorOrClass
58
+ }
59
+ }
package/src/goTry.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type { Result } from './types.js'
2
+ import { success, failure } from './result-helpers.js'
3
+ import { isPromise, getErrorMessage } from './internals.js'
4
+
5
+ /**
6
+ * Executes a function, promise, or value and returns a Result type.
7
+ * If an error occurs, it returns a Failure with the error message as a string.
8
+ *
9
+ * @template T The type of the successful result
10
+ * @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
11
+ * @returns {Result<string, T> | Promise<Result<string, T>>} A Result type or a Promise of a Result type
12
+ *
13
+ * @example
14
+ * // With a value
15
+ * const [err, result] = goTry(42);
16
+ *
17
+ * @example
18
+ * // With a function
19
+ * const [err, result] = goTry(() => JSON.parse('{"key": "value"}'));
20
+ *
21
+ * @example
22
+ * // With a promise
23
+ * const [err, result] = await goTry(fetch('https://api.example.com/data'));
24
+ */
25
+ export function goTry<T>(fn: () => never): Result<string, never>
26
+ export function goTry<T>(fn: () => Promise<T>): Promise<Result<string, T>>
27
+ export function goTry<T>(promise: Promise<T>): Promise<Result<string, T>>
28
+ export function goTry<T>(fn: () => T): Result<string, T>
29
+ export function goTry<T>(value: T): Result<string, T>
30
+ export function goTry<T>(
31
+ value: T | Promise<T> | (() => T | Promise<T>),
32
+ ): Result<string, T> | Promise<Result<string, T>> {
33
+ try {
34
+ const result =
35
+ typeof value === 'function' ? (value as () => T | Promise<T>)() : value
36
+ if (isPromise<T>(result)) {
37
+ return result
38
+ .then((resolvedValue) => success<T>(resolvedValue))
39
+ .catch((err) => failure<string>(getErrorMessage(err)))
40
+ }
41
+ return success<T>(result)
42
+ } catch (err) {
43
+ return failure<string>(getErrorMessage(err))
44
+ }
45
+ }
@@ -0,0 +1,141 @@
1
+ import type { GoTryAllOptions } from './types.js'
2
+ import { isError, getErrorMessage, type PromiseFactory } from './internals.js'
3
+
4
+ async function runWithConcurrency<T extends readonly unknown[]>(
5
+ items: { [K in keyof T]: Promise<T[K]> | PromiseFactory<T[K]> },
6
+ concurrency: number,
7
+ ): Promise<PromiseSettledResult<T[number]>[]> {
8
+ if (items.length === 0) {
9
+ return []
10
+ }
11
+
12
+ // Auto-detect factory mode by checking if first item is a function
13
+ const isFactoryMode = typeof items[0] === 'function'
14
+
15
+ // concurrency of 0 means unlimited (run all in parallel)
16
+ if (!isFactoryMode && (concurrency <= 0)) {
17
+ return Promise.allSettled(items as Promise<T[number]>[])
18
+ }
19
+
20
+ const results: PromiseSettledResult<T[number]>[] = new Array(items.length)
21
+ let index = 0
22
+
23
+ async function worker(): Promise<void> {
24
+ while (index < items.length) {
25
+ const currentIndex = index++
26
+ try {
27
+ const item = items[currentIndex]
28
+ // If factory mode, call the function; otherwise await the promise directly
29
+ const value = isFactoryMode
30
+ ? await (item as PromiseFactory<T[number]>)()
31
+ : await (item as Promise<T[number]>)
32
+ results[currentIndex] = { status: 'fulfilled', value }
33
+ } catch (reason) {
34
+ results[currentIndex] = { status: 'rejected', reason }
35
+ }
36
+ }
37
+ }
38
+
39
+ // Determine number of workers
40
+ const workerCount = concurrency <= 0 ? items.length : Math.min(concurrency, items.length)
41
+
42
+ // Start workers
43
+ const workers: Promise<void>[] = []
44
+ for (let i = 0; i < workerCount; i++) {
45
+ workers.push(worker())
46
+ }
47
+
48
+ await Promise.all(workers)
49
+ return results
50
+ }
51
+
52
+ /**
53
+ * Executes multiple promises or factory functions in parallel (or with limited concurrency)
54
+ * and returns a tuple of [errors, results]. Unlike Promise.all, this doesn't fail fast -
55
+ * it waits for all promises to settle.
56
+ *
57
+ * Accepts either:
58
+ * - An array of promises (for simple parallel execution)
59
+ * - An array of factory functions that return promises (for lazy execution with concurrency control)
60
+ *
61
+ * @template T The tuple type of all promise results
62
+ * @param {readonly [...{ [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) }]} items - Array of promises or factories
63
+ * @param {GoTryAllOptions} options - Optional configuration
64
+ * @returns {Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]>}
65
+ * A tuple where the first element is a tuple of errors (or undefined) and
66
+ * the second element is a tuple of results (or undefined), preserving input order
67
+ *
68
+ * @example
69
+ * // Run all in parallel (default) - with promises
70
+ * const [errors, results] = await goTryAll([
71
+ * fetchUser(1),
72
+ * fetchUser(2),
73
+ * fetchUser(3)
74
+ * ])
75
+ *
76
+ * @example
77
+ * // Limit concurrency with factory functions (lazy execution)
78
+ * const [errors, results] = await goTryAll([
79
+ * () => fetchUser(1), // Only called when a slot is available
80
+ * () => fetchUser(2), // Only called when a slot is available
81
+ * () => fetchUser(3), // Only called when a slot is available
82
+ * ], { concurrency: 2 })
83
+ */
84
+ export async function goTryAll<T extends readonly unknown[]>(
85
+ items: { [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) },
86
+ options?: GoTryAllOptions,
87
+ ): Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]> {
88
+ const settled = await runWithConcurrency(items, options?.concurrency ?? 0)
89
+
90
+ const errors = [] as { [K in keyof T]: string | undefined }
91
+ const results = [] as { [K in keyof T]: T[K] | undefined }
92
+
93
+ for (let i = 0; i < settled.length; i++) {
94
+ const item = settled[i]!
95
+ if (item.status === 'fulfilled') {
96
+ ;(errors as (string | undefined)[])[i] = undefined
97
+ ;(results as unknown[])[i] = (item as PromiseFulfilledResult<T[number]>).value
98
+ } else {
99
+ ;(errors as (string | undefined)[])[i] = getErrorMessage((item as PromiseRejectedResult).reason)
100
+ ;(results as unknown[])[i] = undefined
101
+ }
102
+ }
103
+
104
+ return [errors, results]
105
+ }
106
+
107
+ /**
108
+ * Like `goTryAll`, but returns raw Error objects instead of error messages.
109
+ *
110
+ * @template T The tuple type of all promise results
111
+ * @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 }]>}
114
+ * A tuple where the first element is a tuple of Error objects (or undefined) and
115
+ * the second element is a tuple of results (or undefined), preserving input order
116
+ */
117
+ export async function goTryAllRaw<T extends readonly unknown[]>(
118
+ 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 }]> {
121
+ const settled = await runWithConcurrency(items, options?.concurrency ?? 0)
122
+
123
+ const errors = [] as { [K in keyof T]: Error | undefined }
124
+ const results = [] as { [K in keyof T]: T[K] | undefined }
125
+
126
+ for (let i = 0; i < settled.length; i++) {
127
+ const item = settled[i]!
128
+ if (item.status === 'fulfilled') {
129
+ ;(errors as (Error | undefined)[])[i] = undefined
130
+ ;(results as unknown[])[i] = (item as PromiseFulfilledResult<T[number]>).value
131
+ } else {
132
+ const reason = (item as PromiseRejectedResult).reason
133
+ ;(errors as (Error | undefined)[])[i] = isError(reason)
134
+ ? reason
135
+ : new Error(String(reason))
136
+ ;(results as unknown[])[i] = undefined
137
+ }
138
+ }
139
+
140
+ return [errors, results]
141
+ }
package/src/goTryOr.ts ADDED
@@ -0,0 +1,55 @@
1
+ import type { ResultWithDefault } from './types.js'
2
+ import { success } from './result-helpers.js'
3
+ import { isPromise, getErrorMessage, resolveDefault } from './internals.js'
4
+
5
+ /**
6
+ * Executes a function, promise, or value and returns a Result type with a fallback default.
7
+ * If an error occurs, it returns the error message and the default value.
8
+ *
9
+ * @template T The type of the successful result
10
+ * @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
11
+ * @param {T | (() => T)} defaultValue - The default value or a function to compute it (only called on failure)
12
+ * @returns {ResultWithDefault<string, T> | Promise<ResultWithDefault<string, T>>} A tuple of [error, value] or Promise thereof
13
+ *
14
+ * @example
15
+ * // With a static default
16
+ * const [err, config] = goTryOr(() => JSON.parse('invalid'), { port: 3000 })
17
+ * // err is the error message, config is { port: 3000 }
18
+ *
19
+ * @example
20
+ * // With a computed default (lazy evaluation)
21
+ * const [err, user] = await goTryOr(fetchUser(id), () => ({
22
+ * id: 'anonymous',
23
+ * name: 'Guest'
24
+ * }))
25
+ */
26
+ export function goTryOr<T>(fn: () => never, defaultValue: T | (() => T)): ResultWithDefault<string, T>
27
+ export function goTryOr<T>(
28
+ fn: () => Promise<T>,
29
+ defaultValue: T | (() => T),
30
+ ): Promise<ResultWithDefault<string, T>>
31
+ export function goTryOr<T>(
32
+ promise: Promise<T>,
33
+ defaultValue: T | (() => T),
34
+ ): Promise<ResultWithDefault<string, T>>
35
+ export function goTryOr<T>(fn: () => T, defaultValue: T | (() => T)): ResultWithDefault<string, T>
36
+ export function goTryOr<T>(value: T, defaultValue: T | (() => T)): ResultWithDefault<string, T>
37
+ export function goTryOr<T>(
38
+ value: T | Promise<T> | (() => T | Promise<T>),
39
+ defaultValue: T | (() => T),
40
+ ): ResultWithDefault<string, T> | Promise<ResultWithDefault<string, T>> {
41
+ try {
42
+ const result =
43
+ typeof value === 'function' ? (value as () => T | Promise<T>)() : value
44
+
45
+ if (isPromise<T>(result)) {
46
+ return result
47
+ .then((resolvedValue) => success<T>(resolvedValue))
48
+ .catch((err) => [getErrorMessage(err), resolveDefault(defaultValue)] as const)
49
+ }
50
+
51
+ return success<T>(result)
52
+ } catch (err) {
53
+ return [getErrorMessage(err), resolveDefault(defaultValue)] as const
54
+ }
55
+ }
@@ -0,0 +1,126 @@
1
+ import type { Result, GoTryRawOptions } from './types.js'
2
+ import { success, failure } from './result-helpers.js'
3
+ import { isPromise, isError } from './internals.js'
4
+ import { UnknownError } from './unknown-error.js'
5
+
6
+ /**
7
+ * Checks if a value is a tagged error (has a _tag property).
8
+ */
9
+ function isTaggedError(err: unknown): err is { _tag: string } {
10
+ return isError(err) && '_tag' in err && typeof (err as { _tag?: unknown })._tag === 'string'
11
+ }
12
+
13
+ /**
14
+ * Executes a function, promise, or value and returns a Result type.
15
+ * If an error occurs, it returns a Failure with the raw error object.
16
+ *
17
+ * @template T The type of the successful result
18
+ * @template E The type of the error
19
+ * @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
20
+ * @param {GoTryRawOptions<E>} [options] - Optional options object
21
+ * @returns {Result<E, T> | Promise<Result<E, T>>} A Result type or a Promise of a Result type
22
+ *
23
+ * @example
24
+ * // With a value
25
+ * const [err, result] = goTryRaw(42);
26
+ *
27
+ * @example
28
+ * // With a function
29
+ * const [err, result] = goTryRaw(() => JSON.parse('{"key": "value"}'));
30
+ *
31
+ * @example
32
+ * // With a promise
33
+ * const [err, result] = await goTryRaw(fetch('https://api.example.com/data'));
34
+ *
35
+ * @example
36
+ * // With options object - wrap all errors
37
+ * const DatabaseError = taggedError('DatabaseError');
38
+ * const [err, result] = await goTryRaw(fetchData(), { errorClass: DatabaseError });
39
+ *
40
+ * @example
41
+ * // With options object - systemErrorClass only wraps non-tagged errors
42
+ * const [err, result] = await goTryRaw(fetchData(), { systemErrorClass: UnknownError });
43
+ * // Errors thrown as tagged errors pass through
44
+ * // Other errors are wrapped in UnknownError
45
+ */
46
+ export function goTryRaw<T>(fn: () => never): Result<Error, never>
47
+ export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => never, options: GoTryRawOptions<E>): Result<E, never>
48
+ export function goTryRaw<T>(
49
+ fn: () => Promise<T>,
50
+ ): Promise<Result<Error, T>>
51
+ export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(
52
+ fn: () => Promise<T>,
53
+ options: GoTryRawOptions<E>,
54
+ ): Promise<Result<E, T>>
55
+ export function goTryRaw<T>(
56
+ promise: Promise<T>,
57
+ ): Promise<Result<Error, T>>
58
+ export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(
59
+ promise: Promise<T>,
60
+ options: GoTryRawOptions<E>,
61
+ ): Promise<Result<E, T>>
62
+ export function goTryRaw<T>(fn: () => T): Result<Error, T>
63
+ export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => T, options: GoTryRawOptions<E>): Result<E, T>
64
+ export function goTryRaw<T>(value: T): Result<Error, T>
65
+ export function goTryRaw<T, E = InstanceType<typeof UnknownError>>(value: T, options: GoTryRawOptions<E>): Result<E, T>
66
+ export function goTryRaw<T, E = Error>(
67
+ value: T | Promise<T> | (() => T | Promise<T>),
68
+ options?: GoTryRawOptions<E>,
69
+ ): Result<E, T> | Promise<Result<E, T>> {
70
+ const { errorClass, systemErrorClass } = options || {}
71
+
72
+ // Determine the actual system error class to use
73
+ // Default to UnknownError when systemErrorClass is not specified
74
+ const actualSystemErrorClass = systemErrorClass ?? UnknownError
75
+
76
+ // Helper to wrap error based on the options
77
+ const wrapError = (err: unknown): E => {
78
+ // If errorClass is specified, wrap all errors with it
79
+ if (errorClass) {
80
+ if (err === undefined) {
81
+ return new errorClass('undefined')
82
+ }
83
+ if (isError(err)) {
84
+ return new errorClass(err.message, { cause: err })
85
+ }
86
+ return new errorClass(String(err))
87
+ }
88
+
89
+ // If actualSystemErrorClass is specified, only wrap non-tagged errors
90
+ if (actualSystemErrorClass) {
91
+ if (isTaggedError(err)) {
92
+ return err as unknown as E
93
+ }
94
+
95
+ // Wrap non-tagged errors with systemErrorClass
96
+ if (err === undefined) {
97
+ return new actualSystemErrorClass('undefined') as unknown as E
98
+ }
99
+ if (isError(err)) {
100
+ return new actualSystemErrorClass(err.message, { cause: err }) as unknown as E
101
+ }
102
+ return new actualSystemErrorClass(String(err)) as unknown as E
103
+ }
104
+
105
+ // No options - original behavior: return raw error
106
+ if (err === undefined) {
107
+ return new Error('undefined') as unknown as E
108
+ }
109
+ return (
110
+ isError(err) ? err : new Error(String(err))
111
+ ) as unknown as E
112
+ }
113
+
114
+ try {
115
+ const result =
116
+ typeof value === 'function' ? (value as () => T | Promise<T>)() : value
117
+ if (isPromise<T>(result)) {
118
+ return result
119
+ .then((resolvedValue) => success<T>(resolvedValue))
120
+ .catch((err) => failure<E>(wrapError(err)))
121
+ }
122
+ return success<T>(result)
123
+ } catch (err) {
124
+ return failure<E>(wrapError(err))
125
+ }
126
+ }