Files
CreaBook/node_modules/better-result/dist/index.d.mts
2026-04-05 03:08:53 +02:00

749 lines
23 KiB
TypeScript

//#region src/error.d.ts
/** Any tagged error (for generic constraints) */
type AnyTaggedError = Error & {
readonly _tag: string;
};
/**
* Factory for tagged error classes.
*
* @example
* class NotFoundError extends TaggedError("NotFoundError")<{
* id: string;
* message: string;
* }>() {}
*
* const err = new NotFoundError({ id: "123", message: "Not found: 123" });
* err._tag // "NotFoundError"
* err.id // "123"
* err.message // "Not found: 123"
*
* // Check if any tagged error
* TaggedError.is(err) // true
*/
declare const TaggedError: {
<Tag extends string>(tag: Tag): <Props extends Record<string, unknown> = {}>() => TaggedErrorClass<Tag, Props>;
/** Type guard for any TaggedError instance */
is(value: unknown): value is AnyTaggedError;
};
/** Instance type produced by TaggedError factory */
type TaggedErrorInstance<Tag extends string, Props> = Error & {
readonly _tag: Tag;
toJSON(): object;
} & Readonly<Props>;
/** Class type produced by TaggedError factory */
type TaggedErrorClass<Tag extends string, Props> = {
new (...args: keyof Props extends never ? [args?: {}] : [args: Props]): TaggedErrorInstance<Tag, Props>;
/** Type guard for this error class */
is(value: unknown): value is TaggedErrorInstance<Tag, Props>;
};
/** Handler map for exhaustive matching */
type MatchHandlers<E extends AnyTaggedError, R> = { [K in E["_tag"]]: (err: Extract<E, {
_tag: K;
}>) => R };
/** Partial handler map for non-exhaustive matching */
type PartialMatchHandlers<E extends AnyTaggedError, R> = Partial<MatchHandlers<E, R>>;
/** Extract handled tags from a handlers object */
type HandledTags<E extends AnyTaggedError, H> = Extract<keyof H, E["_tag"]>;
/**
* Exhaustive pattern match on tagged error union.
*
* @example
* // Data-first
* matchError(err, {
* NotFoundError: (e) => `Missing: ${e.id}`,
* ValidationError: (e) => `Invalid: ${e.field}`,
* });
*
* // Data-last (pipeable)
* pipe(err, matchError({
* NotFoundError: (e) => `Missing: ${e.id}`,
* ValidationError: (e) => `Invalid: ${e.field}`,
* }));
*/
declare const matchError: {
<E extends AnyTaggedError, R>(err: E, handlers: MatchHandlers<E, R>): R;
<E extends AnyTaggedError, R>(handlers: MatchHandlers<E, R>): (err: E) => R;
};
/**
* Partial pattern match with fallback for unhandled tags.
*
* @example
* matchErrorPartial(err, {
* NotFoundError: (e) => `Missing: ${e.id}`,
* }, (e) => `Unknown: ${e.message}`);
*/
declare const matchErrorPartial: {
<E extends AnyTaggedError, R, const H extends PartialMatchHandlers<E, R>>(err: E, handlers: H, fallback: (e: Exclude<E, {
_tag: NoInfer<HandledTags<E, H>>;
}>) => R): R;
<E extends AnyTaggedError, R, const H extends PartialMatchHandlers<E, R> = PartialMatchHandlers<E, R>>(handlers: H, fallback: (e: Exclude<E, {
_tag: NoInfer<HandledTags<E, H>>;
}>) => R): (err: E) => R;
};
/**
* Type guard for tagged error instances.
*
* @example
* if (isTaggedError(value)) { value._tag }
*/
declare const isTaggedError: (value: unknown) => value is AnyTaggedError;
declare const UnhandledException_base: TaggedErrorClass<"UnhandledException", {
message: string;
cause: unknown;
}>;
/**
* Wraps exceptions caught by Result.try/tryPromise.
* Custom constructor derives message from cause.
*/
declare class UnhandledException extends UnhandledException_base {
constructor(args: {
cause: unknown;
});
}
declare const Panic_base: TaggedErrorClass<"Panic", {
message: string;
cause?: unknown;
}>;
/**
* Unrecoverable error — user code threw inside Result operations.
*
* @example
* // Panic in generator cleanup:
* Result.gen(function* () {
* try {
* yield* Result.err("expected error");
* } finally {
* throw new Error("cleanup failed"); // Panic!
* }
* });
*
* // Panic in combinator:
* Result.ok(1).map(() => { throw new Error("oops"); }); // Panic!
*/
declare class Panic extends Panic_base {}
declare const ResultDeserializationError_base: TaggedErrorClass<"ResultDeserializationError", {
message: string;
value: unknown;
}>;
/**
* Returned when Result.deserialize receives invalid input.
*
* @example
* const result = Result.deserialize(invalidData);
* if (Result.isError(result) && ResultDeserializationError.is(result.error)) {
* console.log("Invalid input:", result.error.value);
* }
*/
declare class ResultDeserializationError extends ResultDeserializationError_base {
constructor(args: {
value: unknown;
});
}
/**
* Type guard for Panic instances.
*
* @example
* if (isPanic(value)) { value.cause }
*/
declare const isPanic: (value: unknown) => value is Panic;
/**
* Throw an unrecoverable Panic.
*
* @example
* panic("something went wrong", cause);
*/
declare const panic: (message: string, cause?: unknown) => never;
//#endregion
//#region src/result.d.ts
/**
* Successful result variant.
*
* @template A Success value type.
* @template E Error type (phantom - for type unification).
*
* @example
* const result = new Ok(42);
* result.value // 42
* result.status // "ok"
*/
declare class Ok<A, E = never> {
readonly value: A;
readonly status: "ok";
constructor(value: A);
/** Returns true, narrowing Result to Ok. */
isOk(): this is Ok<A, E>;
/** Returns false, narrowing Result to Err. */
isErr(): this is Err<A, E>;
/**
* Transforms success value.
*
* @template B Transformed type.
* @param fn Transformation function.
* @returns Ok with transformed value.
* @throws {Panic} If fn throws.
*
* @example
* ok(2).map(x => x * 2) // Ok(4)
*/
map<B>(fn: (a: A) => B): Ok<B, E>;
/**
* No-op on Ok, returns self with new phantom error type.
*
* @template E2 New error type.
* @param _fn Ignored.
* @returns Self with updated phantom E type.
*/
mapError<E2>(_fn: (e: never) => E2): Ok<A, E2>;
/**
* Chains Result-returning function.
*
* @template B New success type.
* @template E2 New error type.
* @param fn Function returning Result.
* @returns Result from fn.
* @throws {Panic} If fn throws.
*
* @example
* ok(2).andThen(x => x > 0 ? ok(x) : err("negative")) // Ok(2)
*/
andThen<B, E2>(fn: (a: A) => Result<B, E2>): Result<B, E | E2>;
/**
* Chains async Result-returning function.
*
* @template B New success type.
* @template E2 New error type.
* @param fn Async function returning Result.
* @returns Promise of Result from fn.
* @throws {Panic} If fn throws synchronously or rejects.
*
* @example
* await ok(1).andThenAsync(async x => ok(await fetchData(x)))
*/
andThenAsync<B, E2>(fn: (a: A) => Promise<Result<B, E2>>): Promise<Result<B, E | E2>>;
/**
* Pattern matches on Result.
*
* @template T Return type.
* @param handlers Ok and err handlers.
* @returns Result of ok handler.
* @throws {Panic} If handler throws.
*
* @example
* ok(2).match({ ok: x => x * 2, err: () => 0 }) // 4
*/
match<T>(handlers: {
ok: (a: A) => T;
err: (e: never) => T;
}): T;
/**
* Extracts value.
*
* @param _message Ignored.
* @returns The value.
*
* @example
* ok(42).unwrap() // 42
*/
unwrap(_message?: string): A;
/**
* Returns value, ignoring fallback.
*
* @template B Fallback type.
* @param _fallback Ignored.
* @returns The value.
*
* @example
* ok(42).unwrapOr(0) // 42
*/
unwrapOr<B>(_fallback: B): A;
/**
* Runs side effect, returns self.
*
* @param fn Side effect function.
* @returns Self.
* @throws {Panic} If fn throws.
*
* @example
* ok(2).tap(console.log).map(x => x * 2) // logs 2, returns Ok(4)
*/
tap(fn: (a: A) => void): Ok<A, E>;
/**
* Runs async side effect, returns self.
*
* @param fn Async side effect function.
* @returns Promise of self.
* @throws {Panic} If fn throws synchronously or rejects.
*
* @example
* await ok(2).tapAsync(async x => await log(x))
*/
tapAsync(fn: (a: A) => Promise<void>): Promise<Ok<A, E>>;
/**
* Makes Ok yieldable in Result.gen blocks.
* Immediately returns the value without yielding.
* Yield type Err<never, E> matches Err's for proper union inference.
*/
[Symbol.iterator](): Generator<Err<never, E>, A, unknown>;
}
/**
* Error result variant.
*
* @template T Success type (phantom - for type unification with Ok).
* @template E Error value type.
*
* @example
* const result = new Err("failed");
* result.error // "failed"
* result.status // "error"
*/
declare class Err<T, E> {
readonly error: E;
readonly status: "error";
constructor(error: E);
/** Returns false, narrowing Result to Ok. */
isOk(): this is Ok<never, E>;
/** Returns true, narrowing Result to Err. */
isErr(): this is Err<T, E>;
/**
* No-op on Err, returns self with new phantom T.
*
* @template U New phantom success type.
* @param _fn Ignored.
* @returns Self.
*/
map<U>(_fn: (a: never) => U): Err<U, E>;
/**
* Transforms error value.
*
* @template E2 Transformed error type.
* @param fn Transformation function.
* @returns Err with transformed error.
* @throws {Panic} If fn throws.
*
* @example
* err("fail").mapError(e => new Error(e)) // Err(Error("fail"))
*/
mapError<E2>(fn: (e: E) => E2): Err<T, E2>;
/**
* No-op on Err, returns self with widened error type.
*
* @template U New phantom success type.
* @template E2 Additional error type.
* @param _fn Ignored.
* @returns Self.
*/
andThen<U, E2>(_fn: (a: never) => Result<U, E2>): Err<U, E | E2>;
/**
* No-op on Err, returns Promise of self with widened error type.
*
* @template U New phantom success type.
* @template E2 Additional error type.
* @param _fn Ignored.
* @returns Promise of self.
*/
andThenAsync<U, E2>(_fn: (a: never) => Promise<Result<U, E2>>): Promise<Err<U, E | E2>>;
/**
* Pattern matches on Result.
*
* @template R Return type.
* @param handlers Ok and err handlers.
* @returns Result of err handler.
* @throws {Panic} If handler throws.
*
* @example
* err("fail").match({ ok: x => x, err: e => e.length }) // 4
*/
match<R>(handlers: {
ok: (a: never) => R;
err: (e: E) => R;
}): R;
/**
* Throws error with optional message.
*
* @param message Error message.
* @throws Always throws.
*
* @example
* err("fail").unwrap() // throws Error
* err("fail").unwrap("custom") // throws Error("custom")
*/
unwrap(message?: string): never;
/**
* Returns fallback value.
*
* @template U Fallback type.
* @param fallback Fallback value.
* @returns Fallback.
*
* @example
* err("fail").unwrapOr(42) // 42
*/
unwrapOr<U>(fallback: U): T | U;
/**
* No-op on Err, returns self.
*
* @param _fn Ignored.
* @returns Self.
*/
tap(_fn: (a: never) => void): Err<T, E>;
/**
* No-op on Err, returns Promise of self.
*
* @param _fn Ignored.
* @returns Promise of self.
*/
tapAsync(_fn: (a: never) => Promise<void>): Promise<Err<T, E>>;
/**
* Makes Err yieldable in Result.gen blocks.
* Yields Err<never, E> for proper union inference across multiple yields.
*/
[Symbol.iterator](): Generator<Err<never, E>, never, unknown>;
}
/**
* Discriminated union representing operation success or failure.
*
* Both Ok and Err carry phantom types for the "other" variant:
* - Ok<T, E>: T is value, E is phantom error type
* - Err<T, E>: T is phantom success type, E is error
*
* This symmetric structure enables proper type inference in generator-based composition.
*
* @template T Success value type.
* @template E Error value type.
*
* @example
* type ParseResult = Result<number, ParseError>;
*/
type Result<T, E> = Ok<T, E> | Err<T, E>;
/**
* Extracts error type E from yield union in Result.gen.
* Yields are always Err<never, E>, so we match on that pattern.
* Distributive conditional: InferYieldErr<Err<never, A> | Err<never, B>> = A | B
*/
type InferYieldErr<Y> = Y extends Err<never, infer E> ? E : never;
/**
* Infer the Ok value type from a Result.
* Distributive: InferOk<Ok<A, X> | Ok<B, Y>> = A | B
*/
type InferOk<R> = R extends Ok<infer T, unknown> ? T : never;
/**
* Infer the Err value type from a Result.
* Distributive: InferErr<Err<X, A> | Err<Y, B>> = A | B
*/
type InferErr<R> = R extends Err<unknown, infer E> ? E : never;
/**
* Constraint for any union of Ok/Err types.
* Used in Result.gen to accept flexible return types from generators.
*/
type AnyResult = Ok<unknown, unknown> | Err<unknown, unknown>;
declare function ok(): Ok<void, never>;
declare function ok<A, E = never>(value: A): Ok<A, E>;
type RetryConfig<E = unknown> = {
retry?: {
times: number;
delayMs: number;
backoff: "linear" | "constant" | "exponential";
/** Predicate to determine if an error should trigger a retry. Defaults to always retry. */
shouldRetry?: (error: E) => boolean;
};
};
declare function resultAwait<T, E>(promise: Promise<Result<T, E>>): AsyncGenerator<Err<never, E>, T, unknown>;
/** Shape of a serialized Ok over RPC. */
interface SerializedOk<T> {
status: "ok";
value: T;
}
/** Shape of a serialized Err over RPC. */
interface SerializedErr<E> {
status: "error";
error: E;
}
/** Shape of a serialized Result over RPC. */
type SerializedResult<T, E> = SerializedOk<T> | SerializedErr<E>;
/**
* Utilities for creating and handling Result types.
*
* @example
* const result = Result.try(() => JSON.parse(str));
* const value = result.map(x => x.id).unwrapOr("default");
*/
declare const Result: {
/**
* Creates successful result.
*
* @example
* Result.ok(42) // Ok<number, never>
* Result.ok() // Ok<void, never> - for side-effectful operations
*/
readonly ok: typeof ok;
/**
* Type guard for Ok.
*
* @example
* if (Result.isOk(result)) { result.value }
*/
readonly isOk: <A, E>(result: Result<A, E>) => result is Ok<A, E>;
/**
* Creates error result.
*
* @example
* Result.err("failed") // Err("failed")
*/
readonly err: <T = never, E = unknown>(error: E) => Err<T, E>;
/**
* Type guard for Err.
*
* @example
* if (Result.isError(result)) { result.error }
*/
readonly isError: <T, E>(result: Result<T, E>) => result is Err<T, E>;
/**
* Executes sync function, wraps result/error in Result.
*
* @example
* Result.try(() => JSON.parse(str))
* Result.try({ try: () => parse(x), catch: e => new ParseError(e) })
*/
readonly try: {
<A>(thunk: () => Awaited<A>, config?: {
retry?: {
times: number;
};
}): Result<A, UnhandledException>;
<A, E>(options: {
try: () => Awaited<A>;
catch: (cause: unknown) => Awaited<E>;
}, config?: {
retry?: {
times: number;
};
}): Result<A, E>;
};
/**
* Executes async function, wraps result/error in Result with retry support.
*
* @example
* // Basic retry
* await Result.tryPromise(() => fetch(url), {
* retry: { times: 3, delayMs: 100, backoff: "exponential" }
* })
*
* @example
* // Retry only for specific error types (user-defined TaggedError classes)
* await Result.tryPromise({
* try: () => fetch(url),
* catch: e => e instanceof TypeError ? new RetryableError(e) : new FatalError(e)
* }, {
* retry: {
* times: 3,
* delayMs: 100,
* backoff: "exponential",
* shouldRetry: e => e._tag === "RetryableError"
* }
* })
*
* @example
* // Async retry decisions: enrich error in catch handler
* await Result.tryPromise({
* try: () => callApi(url),
* catch: async (e) => {
* const limited = await redis.get(`ratelimit:${userId}`);
* return new ApiError({ cause: e, rateLimited: !!limited });
* }
* }, {
* retry: { times: 3, delayMs: 100, backoff: "exponential", shouldRetry: e => !e.rateLimited }
* })
*/
readonly tryPromise: {
<A>(thunk: () => Promise<A>, config?: RetryConfig<UnhandledException>): Promise<Result<A, UnhandledException>>;
<A, E>(options: {
try: () => Promise<A>;
catch: (cause: unknown) => E | Promise<E>;
}, config?: RetryConfig<E>): Promise<Result<A, E>>;
};
/**
* Transforms success value, passes error through.
*
* @example
* Result.map(ok(2), x => x * 2) // Ok(4)
* Result.map(x => x * 2)(ok(2)) // Ok(4)
*/
readonly map: {
<A, B, E>(result: Result<A, E>, fn: (a: A) => B): Result<B, E>;
<A, B>(fn: (a: A) => B): <E>(result: Result<A, E>) => Result<B, E>;
};
/**
* Transforms error value, passes success through.
*
* @example
* Result.mapError(err("fail"), e => new Error(e)) // Err(Error("fail"))
*/
readonly mapError: {
<A, E, E2>(result: Result<A, E>, fn: (e: E) => E2): Result<A, E2>;
<E, E2>(fn: (e: E) => E2): <A>(result: Result<A, E>) => Result<A, E2>;
};
/**
* Chains Result-returning function on success.
*
* @example
* Result.andThen(ok(2), x => x > 0 ? ok(x) : err("neg")) // Ok(2)
*/
readonly andThen: {
<A, B, E, E2>(result: Result<A, E>, fn: (a: A) => Result<B, E2>): Result<B, E | E2>;
<A, B, E2>(fn: (a: A) => Result<B, E2>): <E>(result: Result<A, E>) => Result<B, E | E2>;
};
/**
* Chains async Result-returning function on success.
*
* @example
* await Result.andThenAsync(ok(1), async x => ok(await fetch(x)))
*/
readonly andThenAsync: {
<A, B, E, E2>(result: Result<A, E>, fn: (a: A) => Promise<Result<B, E2>>): Promise<Result<B, E | E2>>;
<A, B, E2>(fn: (a: A) => Promise<Result<B, E2>>): <E>(result: Result<A, E>) => Promise<Result<B, E | E2>>;
};
/**
* Pattern matches on Result.
*
* @example
* Result.match(ok(2), { ok: x => x * 2, err: () => 0 }) // 4
*/
readonly match: {
<A, E, T>(result: Result<A, E>, handlers: {
ok: (a: A) => T;
err: (e: E) => T;
}): T;
<A, E, T>(handlers: {
ok: (a: A) => T;
err: (e: E) => T;
}): (result: Result<A, E>) => T;
};
/**
* Runs side effect on success value, returns original result.
*
* @example
* Result.tap(ok(2), console.log) // logs 2, returns Ok(2)
*/
readonly tap: {
<A, E>(result: Result<A, E>, fn: (a: A) => void): Result<A, E>;
<A>(fn: (a: A) => void): <E>(result: Result<A, E>) => Result<A, E>;
};
/**
* Runs async side effect on success value, returns original result.
*
* @example
* await Result.tapAsync(ok(2), async x => await log(x))
*/
readonly tapAsync: {
<A, E>(result: Result<A, E>, fn: (a: A) => Promise<void>): Promise<Result<A, E>>;
<A>(fn: (a: A) => Promise<void>): <E>(result: Result<A, E>) => Promise<Result<A, E>>;
};
/**
* Extracts value or throws.
*
* @example
* Result.unwrap(ok(42)) // 42
* Result.unwrap(err("fail")) // throws Error
*/
readonly unwrap: <A, E>(result: Result<A, E>, message?: string) => A;
/**
* Extracts value or returns fallback.
*
* @example
* Result.unwrapOr(ok(42), 0) // 42
* Result.unwrapOr(err("fail"), 0) // 0
*/
readonly unwrapOr: {
<A, E, B>(result: Result<A, E>, fallback: B): A | B;
<B>(fallback: B): <A, E>(result: Result<A, E>) => A | B;
};
/**
* Generator-based composition for Result types.
* Errors from yielded Results form a union; use mapError to normalize.
*
* @example
* const result = Result.gen(function* () {
* const a = yield* getA(); // Err: ErrorA
* const b = yield* getB(a); // Err: ErrorB
* return Result.ok({ a, b });
* });
* // Result<{a, b}, ErrorA | ErrorB>
*
* @example
* // Normalize error types with mapError
* const result = Result.gen(function* () {
* const a = yield* getA();
* const b = yield* getB(a);
* return Result.ok({ a, b });
* }).mapError(e => new UnifiedError(e._tag, e.message));
* // Result<{a, b}, UnifiedError>
*
* @example
* // Async with Result.await
* const result = await Result.gen(async function* () {
* const a = yield* Result.await(fetchA());
* const b = yield* Result.await(fetchB(a));
* return Result.ok({ a, b });
* });
*/
readonly gen: {
<Yield extends Err<never, unknown>, R extends AnyResult>(body: () => Generator<Yield, R, unknown>): Result<InferOk<R>, InferYieldErr<Yield> | InferErr<R>>;
<Yield extends Err<never, unknown>, R extends AnyResult, This>(body: (this: This) => Generator<Yield, R, unknown>, thisArg: This): Result<InferOk<R>, InferYieldErr<Yield> | InferErr<R>>;
<Yield extends Err<never, unknown>, R extends AnyResult>(body: () => AsyncGenerator<Yield, R, unknown>): Promise<Result<InferOk<R>, InferYieldErr<Yield> | InferErr<R>>>;
<Yield extends Err<never, unknown>, R extends AnyResult, This>(body: (this: This) => AsyncGenerator<Yield, R, unknown>, thisArg: This): Promise<Result<InferOk<R>, InferYieldErr<Yield> | InferErr<R>>>;
};
/**
* Wraps Promise<Result> to be yieldable in async Result.gen blocks.
*
* @example
* yield* Result.await(fetchUser(id))
*/
readonly await: typeof resultAwait;
/**
* Converts a Result to a plain object for serialization (e.g., RPC, server actions).
*
* @example
* const serialized = Result.serialize(ok(42)); // { status: "ok", value: 42 }
*/
readonly serialize: <T, E>(result: Result<T, E>) => SerializedResult<T, E>;
/**
* Rehydrates serialized Result from RPC back into Ok/Err instances.
* Returns `Err<ResultDeserializationError>` if the input is not a valid serialized Result.
*
* @example
* // Valid serialized Result
* const result = Result.deserialize<User, AppError>(rpcResponse);
* if (Result.isOk(result)) {
* console.log(result.value); // User
* }
*
* // Invalid input returns ResultDeserializationError
* const invalid = Result.deserialize({ foo: "bar" });
* if (Result.isError(invalid) && ResultDeserializationError.is(invalid.error)) {
* console.log("Bad input:", invalid.error.value);
* }
*/
readonly deserialize: <T, E>(value: unknown) => Result<T, E | ResultDeserializationError>;
/**
* @deprecated Use `Result.deserialize` instead. Will be removed in 3.0.
*/
readonly hydrate: <T, E>(value: unknown) => Result<T, E | ResultDeserializationError>;
/**
* Splits array of Results into tuple of [okValues, errorValues].
*
* @example
* partition([ok(1), err("a"), ok(2)]) // [[1, 2], ["a"]]
*/
readonly partition: <T, E>(results: readonly Result<T, E>[]) => [T[], E[]];
/**
* Flattens nested Result into single Result.
*
* @example
* const nested = Result.ok(Result.ok(42));
* Result.flatten(nested) // Ok(42)
*/
readonly flatten: <T, E, E2>(result: Result<Result<T, E>, E2>) => Result<T, E | E2>;
};
//#endregion
export { Err, type InferErr, type InferOk, Ok, Panic, Result, ResultDeserializationError, type SerializedErr, type SerializedOk, type SerializedResult, TaggedError, type TaggedErrorClass, type TaggedErrorInstance, UnhandledException, isPanic, isTaggedError, matchError, matchErrorPartial, panic };
//# sourceMappingURL=index.d.mts.map