go-go-try
Version:
Tries to execute a sync/async function, returns a result tuple
456 lines (444 loc) • 18.8 kB
text/typescript
/**
* Core Result types for go-go-try
*/
type Success<T> = readonly [undefined, T];
type Failure<E> = readonly [E, undefined];
type Result<E, T> = Success<T> | Failure<E>;
type ResultWithDefault$1<E, T> = readonly [E | undefined, T];
type MaybePromise<T> = T | Promise<T>;
/**
* Base interface for tagged errors.
* The `_tag` property enables discriminated union narrowing.
*/
interface TaggedError<T extends string> {
readonly _tag: T;
readonly message: string;
readonly cause?: unknown;
}
interface GoTryAllOptions {
/**
* Maximum number of concurrent promises.
* Set to 0 (default) for unlimited concurrency (all promises run in parallel).
*/
concurrency?: number;
}
/**
* Type for error constructors that can be used with goTryRaw.
*/
type ErrorConstructor$1<E> = new (message: string, options?: {
cause?: unknown;
}) => E;
/**
* Options for goTryRaw function.
* errorClass and systemErrorClass are mutually exclusive - you can only provide one.
*/
type GoTryRawOptions<E = Error> = {
errorClass: ErrorConstructor$1<E>;
systemErrorClass?: never;
} | {
errorClass?: never;
systemErrorClass: ErrorConstructor$1<E>;
} | {
errorClass?: never;
systemErrorClass?: never;
};
/**
* Options for goTryAllRaw function.
* Includes concurrency control and error class options.
* errorClass and systemErrorClass are mutually exclusive.
*/
type GoTryAllRawOptions<E = Error> = {
concurrency?: number;
errorClass: ErrorConstructor$1<E>;
systemErrorClass?: never;
} | {
concurrency?: number;
errorClass?: never;
systemErrorClass: ErrorConstructor$1<E>;
} | {
concurrency?: number;
errorClass?: never;
systemErrorClass?: never;
};
/**
* Creates a union type from multiple tagged error classes.
*
* @template T A tuple of tagged error class types
* @returns A union of all instance types
*
* @example
* const DatabaseError = taggedError('DatabaseError')
* const NetworkError = taggedError('NetworkError')
*
* type AppError = TaggedUnion<[typeof DatabaseError, typeof NetworkError]>
* // Equivalent to: DatabaseError | NetworkError
*/
type TaggedUnion<T extends readonly ErrorConstructor$1<unknown>[]> = {
[K in keyof T]: T[K] extends ErrorConstructor$1<infer E> ? E : never;
}[number];
/**
* Executes a function, promise, or value and returns a Result type.
* If an error occurs, it returns a Failure with the error message as a string.
*
* @template T The type of the successful result
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
* @returns {Result<string, T> | Promise<Result<string, T>>} A Result type or a Promise of a Result type
*
* @example
* // With a value
* const [err, result] = goTry(42);
*
* @example
* // With a function
* const [err, result] = goTry(() => JSON.parse('{"key": "value"}'));
*
* @example
* // With a promise
* const [err, result] = await goTry(fetch('https://api.example.com/data'));
*/
declare function goTry<T>(fn: () => never): Result<string, never>;
declare function goTry<T>(fn: () => Promise<T>): Promise<Result<string, T>>;
declare function goTry<T>(promise: Promise<T>): Promise<Result<string, T>>;
declare function goTry<T>(fn: () => T): Result<string, T>;
declare function goTry<T>(value: T): Result<string, T>;
/**
* Default system error class for errors that aren't already wrapped in a tagged error class.
*
* @example
* // By default, goTryRaw wraps unknown errors in UnknownError
* const [err, result] = goTryRaw(() => mightThrow())
* if (err) {
* console.log(err._tag) // 'UnknownError'
* }
*
* @example
* // Use a custom system error class
* const SystemError = taggedError('SystemError')
* const [err, result] = goTryRaw(() => mightThrow(), {
* systemErrorClass: SystemError
* })
*/
declare const UnknownError: {
new (message: string, options?: {
cause?: unknown;
} | undefined): {
readonly _tag: "UnknownError";
readonly cause?: unknown;
name: string;
message: string;
stack?: string;
};
isError(error: unknown): error is Error;
};
/**
* Executes a function, promise, or value and returns a Result type.
* If an error occurs, it returns a Failure with the raw error object.
*
* @template T The type of the successful result
* @template E The type of the error
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
* @param {GoTryRawOptions<E>} [options] - Optional options object
* @returns {Result<E, T> | Promise<Result<E, T>>} A Result type or a Promise of a Result type
*
* @example
* // With a value
* const [err, result] = goTryRaw(42);
*
* @example
* // With a function
* const [err, result] = goTryRaw(() => JSON.parse('{"key": "value"}'));
*
* @example
* // With a promise
* const [err, result] = await goTryRaw(fetch('https://api.example.com/data'));
*
* @example
* // With options object - wrap all errors
* const DatabaseError = taggedError('DatabaseError');
* const [err, result] = await goTryRaw(fetchData(), { errorClass: DatabaseError });
*
* @example
* // With options object - systemErrorClass only wraps non-tagged errors
* const [err, result] = await goTryRaw(fetchData(), { systemErrorClass: UnknownError });
* // Errors thrown as tagged errors pass through
* // Other errors are wrapped in UnknownError
*/
declare function goTryRaw<T>(fn: () => never): Result<Error, never>;
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => never, options: GoTryRawOptions<E>): Result<E, never>;
declare function goTryRaw<T>(fn: () => Promise<T>): Promise<Result<Error, T>>;
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => Promise<T>, options: GoTryRawOptions<E>): Promise<Result<E, T>>;
declare function goTryRaw<T>(promise: Promise<T>): Promise<Result<Error, T>>;
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(promise: Promise<T>, options: GoTryRawOptions<E>): Promise<Result<E, T>>;
declare function goTryRaw<T>(fn: () => T): Result<Error, T>;
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(fn: () => T, options: GoTryRawOptions<E>): Result<E, T>;
declare function goTryRaw<T>(value: T): Result<Error, T>;
declare function goTryRaw<T, E = InstanceType<typeof UnknownError>>(value: T, options: GoTryRawOptions<E>): Result<E, T>;
/**
* Executes a function, promise, or value and returns a Result type with a fallback default.
* If an error occurs, it returns the error message and the default value.
*
* @template T The type of the successful result
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
* @param {T | (() => T)} defaultValue - The default value or a function to compute it (only called on failure)
* @returns {ResultWithDefault<string, T> | Promise<ResultWithDefault<string, T>>} A tuple of [error, value] or Promise thereof
*
* @example
* // With a static default
* const [err, config] = goTryOr(() => JSON.parse('invalid'), { port: 3000 })
* // err is the error message, config is { port: 3000 }
*
* @example
* // With a computed default (lazy evaluation)
* const [err, user] = await goTryOr(fetchUser(id), () => ({
* id: 'anonymous',
* name: 'Guest'
* }))
*/
declare function goTryOr<T>(fn: () => never, defaultValue: T | (() => T)): ResultWithDefault$1<string, T>;
declare function goTryOr<T>(fn: () => Promise<T>, defaultValue: T | (() => T)): Promise<ResultWithDefault$1<string, T>>;
declare function goTryOr<T>(promise: Promise<T>, defaultValue: T | (() => T)): Promise<ResultWithDefault$1<string, T>>;
declare function goTryOr<T>(fn: () => T, defaultValue: T | (() => T)): ResultWithDefault$1<string, T>;
declare function goTryOr<T>(value: T, defaultValue: T | (() => T)): ResultWithDefault$1<string, T>;
/**
* Executes multiple promises or factory functions in parallel (or with limited concurrency)
* and returns a tuple of [errors, results]. Unlike Promise.all, this doesn't fail fast -
* it waits for all promises to settle.
*
* Accepts either:
* - An array of promises (for simple parallel execution)
* - An array of factory functions that return promises (for lazy execution with concurrency control)
*
* @template T The tuple type of all promise results
* @param {readonly [...{ [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) }]} items - Array of promises or factories
* @param {GoTryAllOptions} options - Optional configuration
* @returns {Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]>}
* A tuple where the first element is a tuple of errors (or undefined) and
* the second element is a tuple of results (or undefined), preserving input order
*
* @example
* // Run all in parallel (default) - with promises
* const [errors, results] = await goTryAll([
* fetchUser(1),
* fetchUser(2),
* fetchUser(3)
* ])
*
* @example
* // Limit concurrency with factory functions (lazy execution)
* const [errors, results] = await goTryAll([
* () => fetchUser(1), // Only called when a slot is available
* () => fetchUser(2), // Only called when a slot is available
* () => fetchUser(3), // Only called when a slot is available
* ], { concurrency: 2 })
*/
declare function goTryAll<T extends readonly unknown[]>(items: {
[K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>);
}, options?: GoTryAllOptions): Promise<[{
[K in keyof T]: string | undefined;
}, {
[K in keyof T]: T[K] | undefined;
}]>;
/**
* Like `goTryAll`, but returns raw Error objects instead of error messages.
* Non-tagged errors are wrapped in `UnknownError` by default (consistent with `goTryRaw`).
* Tagged errors pass through unchanged.
*
* Supports `errorClass` and `systemErrorClass` options (mutually exclusive):
* - `errorClass`: Wrap ALL errors in the specified class
* - `systemErrorClass`: Only wrap non-tagged errors (defaults to UnknownError)
*
* @template T The tuple type of all promise results
* @template E The type of the error
* @param {readonly [...{ [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) }]} items - Array of promises or factories
* @param {GoTryAllRawOptions<E>} options - Optional configuration
* @returns {Promise<[{ [K in keyof T]: E | undefined }, { [K in keyof T]: T[K] | undefined }]>}
* A tuple where the first element is a tuple of Error objects (or undefined) and
* the second element is a tuple of results (or undefined), preserving input order
*/
declare function goTryAllRaw<T extends readonly unknown[], E = InstanceType<typeof UnknownError>>(items: {
[K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>);
}, options?: GoTryAllRawOptions<E>): Promise<[{
[K in keyof T]: E | undefined;
}, {
[K in keyof T]: T[K] | undefined;
}]>;
/**
* Creates a tagged error class for discriminated error handling.
*
* @template T The literal type of the tag
* @param tag The string tag to identify this error type (e.g., 'DatabaseError')
* @returns A class constructor for creating tagged errors
*
* @example
* const DatabaseError = taggedError('DatabaseError')
* const NetworkError = taggedError('NetworkError')
*
* type MyError = InstanceType<typeof DatabaseError> | InstanceType<typeof NetworkError>
*
* function fetchUser(id: string): Result<MyError, User> {
* const [err, user] = goTryRaw(fetch(`/users/${id}`), DatabaseError)
* if (err) return failure(err)
* // ...
* }
*
* // Pattern matching on errors
* if (err._tag === 'DatabaseError') {
* // TypeScript knows this is DatabaseError
* }
*/
declare function taggedError<T extends string>(tag: T): {
new (message: string, options?: {
cause?: unknown;
}): {
readonly _tag: T;
readonly cause?: unknown;
name: string;
message: string;
stack?: string;
};
isError(error: unknown): error is Error;
};
/**
* Asserts that a condition is true, otherwise throws the provided error.
* Provides type narrowing when used with Result types.
*
* @param condition - The condition to assert
* @param error - An Error instance or string message to throw if condition is falsy
* @throws {Error} Throws the provided error if condition is falsy
*
* @example
* // With Result type - narrows error to undefined after assertion
* const [err, user] = goTryRaw(fetchUser(), DatabaseError)
* assert(err === undefined, new DatabaseError('Failed to fetch user'))
* // TypeScript now knows: err is undefined, user is User
*
* @example
* // With string message
* assert(response.ok, 'Response was not ok')
*
* @example
* // With custom Error instance
* assert(value > 0, new ValidationError('Value must be positive'))
*/
declare function assert(condition: unknown, error: Error | string): asserts condition;
/**
* Asserts that a condition is true, otherwise instantiates and throws the error class.
* Provides type narrowing when used with Result types.
*
* @param condition - The condition to assert
* @param ErrorClass - An Error class constructor (e.g., from taggedError)
* @param message - The error message to pass to the constructor
* @throws {Error} Throws a new instance of ErrorClass if condition is falsy
*
* @example
* const ValidationError = taggedError('ValidationError')
* assert(value > 0, ValidationError, 'Value must be positive')
* // Equivalent to: if (!(value > 0)) throw new ValidationError('Value must be positive')
*/
declare function assert<T extends Error>(condition: unknown, ErrorClass: new (message: string) => T, message: string): asserts condition;
/**
* Error class constructor type.
*/
type ErrorConstructor<E extends Error = Error> = new (message: string, options?: {
cause?: unknown;
}) => E;
/**
* Ensures a value satisfies a predicate, throwing an error if not.
* Returns the value if the predicate passes.
*
* Accepts sync values, promises, or functions - just like `go`.
*
* The error can be either:
* - An Error class constructor (instantiated with the value as cause)
* - A function that creates and returns an Error
* - If omitted, defaults to UnknownError
*
* @example
* ```typescript
* // With sync value (uses UnknownError by default)
* ensure(42, n => n > 0)
*
* // With promise (awaited internally)
* const res = await ensure(fetch('/api'), r => r.ok, RequestFailedError)
*
* // With function
* const res = ensure(() => parseInt('42'), n => !isNaN(n), Error)
*
* // With error factory function
* const res = ensure(
* await fetch('/api'),
* r => r.ok,
* r => new Error(`HTTP ${r.status}`)
* )
* ```
*/
declare function ensure<T>(value: Promise<T>, predicate: (value: T) => boolean, error?: ErrorConstructor<Error> | ((value: T) => Error)): Promise<T>;
declare function ensure<T>(fn: () => Promise<T>, predicate: (value: T) => boolean, error?: ErrorConstructor<Error> | ((value: T) => Error)): Promise<T>;
declare function ensure<T>(fn: () => T, predicate: (value: T) => boolean, error?: ErrorConstructor<Error> | ((value: T) => Error)): T;
declare function ensure<T>(value: T, predicate: (value: T) => boolean, error?: ErrorConstructor<Error> | ((value: T) => Error)): T;
declare function isSuccess<E, T>(result: Result<E, T>): result is Success<T>;
declare function isFailure<E, T>(result: Result<E, T>): result is Failure<E>;
declare function success<T>(value: T): Success<T>;
declare function failure<E>(error: E): Failure<E>;
/**
* Helper for exhaustive switch checks on discriminated unions.
* If this function is called, it means a case was forgotten in a switch statement.
* Use this in the `default` case of switch statements handling tagged errors.
*
* @param value - The value that should be of type `never` if all cases are handled
* @throws {Error} Always throws an error indicating unhandled case
*
* @example
* const DatabaseError = taggedError('DatabaseError')
* const NetworkError = taggedError('NetworkError')
* type AppError = InstanceType<typeof DatabaseError> | InstanceType<typeof NetworkError>
*
* function handleError(err: AppError): string {
* switch (err._tag) {
* case 'DatabaseError':
* return `DB: ${err.message}`
* case 'NetworkError':
* return `NET: ${err.message}`
* default:
* // TypeScript will error if we forget a case above
* return assertNever(err)
* }
* }
*/
declare function assertNever(value: never): never;
/**
* Result type with Error and a default value.
* On error, returns [Error, DefaultT]
* On success, returns [undefined, T]
*/
type ResultWithDefault<E, T, D = T> = readonly [E, D] | readonly [undefined, T];
/**
* Executes a function, promise, or value and returns a Result type with a fallback default.
* If an error occurs, it returns the actual Error object and the default value.
*
* @template T The type of the successful result
* @template D The type of the default value (defaults to T)
* @param {T | Promise<T> | (() => T | Promise<T>)} value - The value, promise, or function to execute
* @param {D | (() => D)} defaultValue - The default value or a function to compute it (only called on failure)
* @returns {ResultWithDefault<Error, T, D> | Promise<ResultWithDefault<Error, T, D>>} A tuple of [error, value] or Promise thereof
*
* @example
* // With a static default
* const [err, config] = goElse(() => JSON.parse('invalid'), { port: 3000 })
* // err is the Error object, config is { port: 3000 }
*
* @example
* // With a computed default (lazy evaluation)
* const [err, user] = await goElse(fetchUser(id), () => ({
* id: 'anonymous',
* name: 'Guest'
* }))
*/
declare function goElse<T, D = T>(fn: () => never, defaultValue: D | (() => D)): ResultWithDefault<Error, never, D>;
declare function goElse<T, D = T>(fn: () => Promise<T>, defaultValue: D | (() => D)): Promise<ResultWithDefault<Error, T, D>>;
declare function goElse<T, D = T>(promise: Promise<T>, defaultValue: D | (() => D)): Promise<ResultWithDefault<Error, T, D>>;
declare function goElse<T, D = T>(fn: () => T, defaultValue: D | (() => D)): ResultWithDefault<Error, T, D>;
declare function goElse<T, D = T>(value: T, defaultValue: D | (() => D)): ResultWithDefault<Error, T, D>;
export { UnknownError, assert, assertNever, ensure, failure, goTryRaw as go, goTryAllRaw as goAll, goElse, goTry, goTryAll, goTryAllRaw, goTryOr, goTryRaw, isFailure, isSuccess, success, taggedError };
export type { ErrorConstructor$1 as ErrorConstructor, Failure, GoTryAllOptions, GoTryAllRawOptions, GoTryRawOptions, MaybePromise, Result, ResultWithDefault$1 as ResultWithDefault, Success, TaggedError, TaggedUnion };
//# sourceMappingURL=index.d.cts.map