66 */
77const ATTEMPT_STATUS = Symbol ( 'attempt:status' ) ;
88
9+ const VALUE_INDEX = 0 ;
10+
11+ const ERROR_INDEX = 1 ;
12+
13+ export const SUCCEEDED = Symbol ( 'attempt:status:succeeded' ) ;
14+ export const FAILED = Symbol ( 'attempt:status:failed' ) ;
15+
16+
917/**
10- * Enum-like object holding internal status symbols for succeeded/failed results.
11- *
12- * @private
13- * @readonly
18+ * Returns the status of an attempt result.
1419 * @enum {unique symbol}
20+ * @property {unique symbol } succeeded - Represents a successful attempt result.
21+ * @property {unique symbol } failed - Represents a failed attempt result.
1522 */
16- const ATTEMPT_STATUSES = Object . freeze ( {
17- succeeded : Symbol ( 'attempt:succeeded' ) ,
18- failed : Symbol ( 'attempt:failed' ) ,
23+ export const ATTEMPT_STATUSES = Object . freeze ( {
24+ succeeded : SUCCEEDED ,
25+ failed : FAILED ,
1926} ) ;
2027
28+ /**
29+ * Gets the status of an attempt result.
30+ *
31+ * @param {AttemptResult } result The result to check.
32+ * @returns {ATTEMPT_STATUSES.succeeded|ATTEMPT_STATUSES.failed }
33+ * @throws {TypeError } If the result is not an `AttemptResult`.
34+ */
35+ export function getAttemptStatus ( result ) {
36+ if ( isAttemptResult ( result ) ) {
37+ return result [ ATTEMPT_STATUS ] ;
38+ } else {
39+ throw new TypeError ( 'Result must be an `AttemptResult` tuple.' ) ;
40+ }
41+ }
42+
43+ /**
44+ * Union of all error types.
45+ * @typedef {Error|DOMException|TypeError|RangeError|AggregateError|ReferenceError|EvalError|URIError|SyntaxError } AnyError
46+ */
47+
48+ /**
49+ * @template T
50+ * @typedef {readonly [T, null] & { [ATTEMPT_STATUS]: typeof ATTEMPT_STATUSES.succeeded } } AttemptSuccess
51+ * Represents a successful outcome tuple with hidden metadata.
52+ */
53+
54+ /**
55+ * @template E
56+ * @typedef {readonly [null, E] & { [ATTEMPT_STATUS]: typeof ATTEMPT_STATUSES.failed } } AttemptFailure
57+ * Represents a failed outcome tuple with hidden metadata.
58+ */
59+
60+ /**
61+ * @template T
62+ * @template E
63+ * @typedef {AttemptSuccess<T> | AttemptFailure<E> } AttemptResult<T, E>
64+ * Union type for both possible attempt outcomes.
65+ */
66+
2167/**
2268 * Attach a hidden status symbol and freeze the result.
2369 *
24- * @template {readonly [any, Error|null]} T
70+ * @template T
2571 * @param {T } value A tuple to tag with metadata.
2672 * @param {symbol } status Internal status symbol.
27- * @returns {T & { [ATTEMPT_STATUS]: symbol } } The frozen and tagged tuple.
73+ * @returns {readonly T & { [ATTEMPT_STATUS]: symbol } } The frozen and tagged tuple.
2874 * @private
2975 */
3076function _createResult ( value , status ) {
@@ -38,6 +84,24 @@ function _createResult(value, status) {
3884 return Object . freeze ( value ) ;
3985}
4086
87+ /**
88+ * @template T
89+ * @param {AttemptSuccess<T> } result
90+ * @returns {T }
91+ */
92+ function _extractValue ( result ) {
93+ return result [ VALUE_INDEX ] ;
94+ }
95+
96+ /**
97+ * @template E
98+ * @param {AttemptFailure<E> } result
99+ * @returns {E }
100+ */
101+ function _extractError ( result ) {
102+ return result [ ERROR_INDEX ] ;
103+ }
104+
41105/**
42106 * @template T
43107 * @param {T } input
@@ -46,93 +110,112 @@ function _createResult(value, status) {
46110const _successHandler = input => input ;
47111
48112/**
49- *
50- * @param {Error } err
51- * @throws {Error }
113+ * @template E
114+ * @param {E } err
115+ * @throws {E }
52116 */
53117const _failHandler = err => {
54- throw new Error ( 'Unhandled error in result.' , { cause : err } ) ;
118+ throw err ;
55119} ;
56120
57121/**
58122 * Returns `true` if the given value is an AttemptResult (a frozen tuple with hidden metadata).
123+ *
59124 * @param {unknown } result The value to check.
60- * @returns {result is AttemptResult<any> }
125+ * @returns {result is AttemptResult }
61126 */
62127export const isAttemptResult = result => Array . isArray ( result ) && Object . hasOwn ( result , ATTEMPT_STATUS ) ;
63128
64129/**
65130 * Returns `true` if the given result is a successful AttemptResult.
131+ *
66132 * @param {unknown } result
67- * @returns {result is AttemptSuccess<any> }
133+ * @returns {result is AttemptSuccess }
68134 */
69135export const succeeded = result => isAttemptResult ( result ) && result [ ATTEMPT_STATUS ] === ATTEMPT_STATUSES . succeeded ;
70136
71137/**
72138 * Returns `true` if the given result is a failed AttemptResult.
139+ *
73140 * @param {unknown } result
74- * @returns {result is AttemptFailure }
141+ * @returns {result is AttemptFailure<AnyError> }
75142 */
76143export const failed = result => isAttemptResult ( result ) && result [ ATTEMPT_STATUS ] === ATTEMPT_STATUSES . failed ;
77144
78145/**
146+ * Creates an `AttemptSuccess`
147+ *
79148 * @template T
80- * @typedef { readonly [T, null] & { [ATTEMPT_STATUS]: typeof ATTEMPT_STATUSES.succeeded } } AttemptSuccess
81- * Represents a successful outcome tuple with hidden metadata.
149+ * @param { T } value
150+ * @returns { AttemptSuccess<T> }
82151 */
152+ export const succeed = value => isAttemptResult ( value ) ? value : _createResult ( [ value , null ] , ATTEMPT_STATUSES . succeeded ) ;
83153
84154/**
85- * @typedef {readonly [null, Error] & { [ATTEMPT_STATUS]: typeof ATTEMPT_STATUSES.failed } } AttemptFailure
86- * Represents a failed outcome tuple with hidden metadata.
155+ * @overload
156+ * @param {string } err
157+ * @returns {AttemptFailure<Error> }
87158 */
88-
89159/**
90- * @template T
91- * @typedef {AttemptSuccess<T> | AttemptFailure } AttemptResult
92- * Union type for both possible attempt outcomes.
160+ * @overload
161+ * @template E
162+ * @param {AttemptFailure<E> } err
163+ * @returns {AttemptFailure<E> }
93164 */
94-
95165/**
96- * Creates an `AttemptSuccess`
97- *
98- * @template T The type of the successful result.
99- * @param {T } value
100- * @returns {AttemptSuccess<T> }
166+ * @overload
167+ * @param {AnyError } err
168+ * @returns {AttemptFailure<AnyError> }
101169 */
102- export const succeed = value => isAttemptResult ( value ) ? value : _createResult ( [ value , null ] , ATTEMPT_STATUSES . succeeded ) ;
103-
104170/**
105171 * Creates an `AttemptFailure`
106- * @param {Error|string|AttemptFailure } err
107- * @returns {AttemptFailure }
172+ *
173+ * @param {AnyError|AttemptFailure<AnyError>|string } err
174+ * @returns {AttemptFailure<AnyError> }
108175 */
109-
110176export function fail ( err ) {
111177 if ( isAttemptResult ( err ) ) {
112178 return err ;
113179 } else if ( Error . isError ( err ) ) {
114180 return _createResult ( [ null , err ] , ATTEMPT_STATUSES . failed ) ;
115- } else {
181+ } else if ( typeof err === 'string' ) {
116182 return _createResult ( [ null , new Error ( err ) ] , ATTEMPT_STATUSES . failed ) ;
183+ } else {
184+ return _createResult ( [ null , new TypeError ( 'Invalid error type provided.' ) ] , ATTEMPT_STATUSES . failed ) ;
117185 }
118186}
119187
120188/**
121- * Extracts the value from a successful `AttemptResult`, or `null` if it failed or is invalid .
189+ * Extracts the value from a successful `AttemptResult`.
122190 *
123191 * @template T
124- * @param {AttemptResult<T> } result The result to extract from.
125- * @returns {T | null } The successful result value, or `null` if not a success.
192+ * @param {AttemptSuccess<T> } result The result to extract from.
193+ * @returns {T } The successful result value.
194+ * @throws {TypeError } If the result is not a successful `AttemptSuccess`.
126195 */
127- export const getResultValue = result => succeeded ( result ) ? result [ 0 ] : null ;
196+ export function getResultValue ( result ) {
197+ if ( succeeded ( result ) ) {
198+ return _extractValue ( result ) ;
199+ } else {
200+ throw new TypeError ( 'Result must be an `AttemptSuccess` tuple.' ) ;
201+ }
202+ }
128203
129204/**
130- * Extracts the error from a failed `AttemptResult`, or `null` if it succeeded or is invalid .
205+ * Extracts the error from a failed `AttemptResult`.
131206 *
132- * @param {AttemptResult } result The result to extract from.
133- * @returns {Error | null } The error object if the result is a failure, otherwise `null`.
207+ * @template E
208+ * @param {AttemptFailure<E> } result The result to extract from.
209+ * @returns {E } The error object if the result is a failure.
210+ * @throws {TypeError } If the result is not a failed `AttemptFailure`.
134211 */
135- export const getResultError = result => failed ( result ) ? result [ 1 ] : null ;
212+ export function getResultError ( result ) {
213+ if ( failed ( result ) ) {
214+ return _extractError ( result ) ;
215+ } else {
216+ throw new TypeError ( 'Result must be an `AttemptFailure` tuple.' ) ;
217+ }
218+ }
136219
137220/**
138221 * Attempts to execute a given callback function, catching any synchronous errors or Promise rejections,
@@ -143,7 +226,7 @@ export const getResultError = result => failed(result) ? result[1] : null;
143226 * @template T
144227 * @param {(...any) => T | PromiseLike<T> } callback The function to execute. It can be synchronous or return a Promise.
145228 * @param {(...any) } args Arguments to pass to the callback function.
146- * @returns {Promise<AttemptResult<Awaited<T>>> } A Promise that resolves to a tuple:
229+ * @returns {Promise<AttemptResult<Awaited<T>|null, AnyError|null >> } A Promise that resolves to a tuple:
147230 * - `[result, null]` on success, where `result` is the resolved value of `T`.
148231 * - `[null, Error]` on failure, where `Error` is the caught error (normalized to an Error object).
149232 * @throws {TypeError } If `callback` is not a function.
@@ -152,7 +235,7 @@ export async function attemptAsync(callback, ...args) {
152235 if ( typeof callback !== 'function' ) {
153236 throw new TypeError ( 'callback must be a function.' ) ;
154237 } else {
155- return await Promise . try ( callback , ...args ) . then ( succeed , fail ) ;
238+ return await Promise . try ( callback , ...args ) . then ( succeed ) . catch ( fail ) ;
156239 }
157240}
158241
@@ -165,7 +248,7 @@ export async function attemptAsync(callback, ...args) {
165248 * @template T
166249 * @param {(...any) => T } callback The function to execute.
167250 * @param {(...any) } args Arguments to pass to the callback function.
168- * @returns {AttemptResult<T> } A tuple:
251+ * @returns {AttemptResult<T, AnyError|null > } A tuple:
169252 * - `[result, null]` on success, where `result` is the value of `T`.
170253 * - `[null, Error]` on failure, where `Error` is the caught error (normalized to an Error object).
171254 * @throws {TypeError } If `callback` is not a function or is an async function.
@@ -191,7 +274,7 @@ export function attemptSync(callback, ...args) {
191274 *
192275 * @template T
193276 * @param {(...any) => T | PromiseLike<T> } callback The function to execute.
194- * @returns {(...any) => Promise<AttemptResult<Awaited<T>>> } An async wrapped function that returns to a tuple:
277+ * @returns {(...any) => Promise<AttemptResult<Awaited<T>|null, AnyError|null> >> } An async wrapped function that returns to a tuple:
195278 * - `[result, null]` on success, where `result` is the value of `T`.
196279 * - `[null, Error]` on failure, where `Error` is the caught error (normalized to an Error object).
197280 * @throws {TypeError } If `callback` is not a function.
@@ -203,7 +286,7 @@ export const createSafeAsyncCallback = callback => async (...args) => await atte
203286 *
204287 * @template T
205288 * @param {(...any) => T } callback The function to execute.
206- * @returns {(...any) => AttemptResult<T> } A wrapped function that returns a tuple:
289+ * @returns {(...any) => AttemptResult<T, AnyError|null > } A wrapped function that returns a tuple:
207290 * - `[result, null]` on success, where `result` is the value of `T`.
208291 * - `[null, Error]` on failure, where `Error` is the caught error (normalized to an Error object).
209292 * @throws {TypeError } If `callback` is not a function or is an async function.
@@ -219,13 +302,16 @@ export const createSafeSyncCallback = callback => (...args) => attemptSync(callb
219302 *
220303 * All callbacks are wrapped in `attemptAsync()` to preserve consistent result formatting and error handling.
221304 *
222- * @template T,U
223- * @param {AttemptResult<T> } result The result to handle.
305+ * @template T
306+ * @template E
307+ * @template U
308+ * @template V
309+ * @param {AttemptResult<T, E> } result The result to handle.
224310 * @param {{
225311 * success?: (value: T) => U | PromiseLike<U>,
226- * failure?: (err: Error ) => U | PromiseLike<U >
312+ * failure?: (err: E ) => V | PromiseLike<V >
227313 * }} callbacks Handlers for success or failure cases.
228- * @returns {Promise<AttemptResult<Awaited<U>>> } A Promise resolving to a new `AttemptResult` from the callback execution,
314+ * @returns {Promise<AttemptResult<Awaited<U>|Awaited<V>, E >> } A Promise resolving to a new `AttemptResult` from the callback execution,
229315 * or a failure if the input is invalid.
230316 */
231317export async function handleResultAsync ( result , {
@@ -250,13 +336,16 @@ export async function handleResultAsync(result, {
250336 *
251337 * All callbacks are wrapped in `attemptSync()` to preserve consistent result formatting and error handling.
252338 *
253- * @template T,U
254- * @param {AttemptResult<T> } result The result to handle.
339+ * @template T
340+ * @template E
341+ * @template U
342+ * @template V
343+ * @param {AttemptResult<T, E> } result The result to handle.
255344 * @param {{
256345 * success?: (value: T) => U,
257- * failure?: (err: Error ) => U
346+ * failure?: (err: E ) => V
258347 * }} callbacks Handlers for success or failure cases.
259- * @returns {AttemptResult<U > } A Promise resolving to a new `AttemptResult` from the callback execution,
348+ * @returns {AttemptSuccess<U>|AttemptSuccess<V>|AttemptFailure<E> > } A Promise resolving to a new `AttemptResult` from the callback execution,
260349 * or a failure if the input is invalid.
261350 */
262351export function handleResultSync ( result , {
@@ -275,8 +364,9 @@ export function handleResultSync(result, {
275364/**
276365 * Throws the error if `result` is an `AttemptFailure`.
277366 *
278- * @param {AttemptResult } result The result tuple
279- * @throws {Error } The error if result is an `AttemptFailure`
367+ * @template E
368+ * @param {AttemptResult<any, AnyError> } result The result tuple
369+ * @throws {AnyError } The error if result is an `AttemptFailure`
280370 */
281371export function throwIfFailed ( result ) {
282372 if ( failed ( result ) ) {
0 commit comments