UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

333 lines (332 loc) • 9.47 kB
import { logger } from "../logger/index.js"; import { NEVER } from "zod/v4"; //#region lib/util/result.ts function isZodResult(input) { if (typeof input !== "object" || input === null || Object.keys(input).length !== 2 || !("success" in input) || typeof input.success !== "boolean") return false; if (input.success) return "data" in input && typeof input.data !== "undefined" && input.data !== null; else return "error" in input; } function fromZodResult(input) { return input.success ? Result.ok(input.data) : Result.err(input.error); } function fromNullable(input, errForNull, errForUndefined) { if (input === null) return Result.err(errForNull); if (input === void 0) return Result.err(errForUndefined); return Result.ok(input); } /** * Class for representing a result that can fail. * * The mental model: * - `.wrap()` and `.wrapNullable()` are sinks * - `.transform()` are pipes which can be chained * - `.unwrap()` is the point of consumption */ var Result = class Result { res; constructor(res) { this.res = res; } static ok(val) { return new Result({ ok: true, val }); } static err(err) { return new Result({ ok: false, err }); } static _uncaught(err) { return new Result({ ok: false, err, _uncaught: true }); } static wrap(input) { if (isZodResult(input)) return fromZodResult(input); if (input instanceof Promise) return AsyncResult.wrap(input); try { const result = input(); if (result instanceof Promise) return AsyncResult.wrap(result); return Result.ok(result); } catch (error) { return Result.err(error); } } static wrapNullable(input, arg2, arg3) { const errForNull = arg2; const errForUndefined = arg3 ?? arg2; if (input instanceof Promise) return AsyncResult.wrapNullable(input, errForNull, errForUndefined); if (input instanceof Function) try { return fromNullable(input(), errForNull, errForUndefined); } catch (error) { return Result.err(error); } return fromNullable(input, errForNull, errForUndefined); } /** * Returns a discriminated union for type-safe consumption of the result. * When error was uncaught during transformation, it's being re-thrown here. * * ```ts * * const { val, err } = Result.ok('foo').unwrap(); * expect(val).toBe('foo'); * expect(err).toBeUndefined(); * * ``` */ unwrap() { if (this.res.ok) return this.res; if (this.res._uncaught) throw this.res.err; return this.res; } /** * Returns a success value or a fallback value. * When error was uncaught during transformation, it's being re-thrown here. * * ```ts * * const value = Result.err('bar').unwrapOr('foo'); * expect(val).toBe('foo'); * * ``` */ unwrapOr(fallback) { if (this.res.ok) return this.res.val; if (this.res._uncaught) throw this.res.err; return fallback; } /** * Returns the ok-value or throw the error. */ unwrapOrThrow() { if (this.res.ok) return this.res.val; throw this.res.err; } /** * Returns the ok-value or `null`. * When error was uncaught during transformation, it's being re-thrown here. */ unwrapOrNull() { if (this.res.ok) return this.res.val; if (this.res._uncaught) throw this.res.err; return null; } transform(fn) { if (!this.res.ok) return Result.err(this.res.err); try { const result = fn(this.res.val); if (result instanceof Result) return result; if (result instanceof AsyncResult) return result; if (isZodResult(result)) return fromZodResult(result); if (result instanceof Promise) return AsyncResult.wrap(result, (err) => { logger.warn({ err }, "Result: unhandled async transform error"); return Result._uncaught(err); }); return Result.ok(result); } catch (err) { logger.warn({ err }, "Result: unhandled transform error"); return Result._uncaught(err); } } catch(fn) { if (this.res.ok) return this; if (this.res._uncaught) return this; try { const result = fn(this.res.err); if (result instanceof Promise) return AsyncResult.wrap(result, (err) => { logger.warn({ err }, "Result: unexpected error in async catch handler"); return Result._uncaught(err); }); return result; } catch (err) { logger.warn({ err }, "Result: unexpected error in catch handler"); return Result._uncaught(err); } } /** * Given a `schema` and `input`, returns a `Result` with `val` being the parsed value. * Additionally, `null` and `undefined` values are converted into Zod error. */ static parse(input, schema) { return fromZodResult(schema.transform((result, ctx) => { if (result === void 0) { ctx.addIssue({ code: "custom", message: `Result can't accept nullish values, but input was parsed by Zod schema to undefined` }); return NEVER; } if (result === null) { ctx.addIssue({ code: "custom", message: `Result can't accept nullish values, but input was parsed by Zod schema to null` }); return NEVER; } return result; }).safeParse(input)); } /** * Given a `schema`, returns a `Result` with `val` being the parsed value. * Additionally, `null` and `undefined` values are converted into Zod error. */ parse(schema) { if (this.res.ok) return Result.parse(this.res.val, schema); const err = this.res.err; if (this.res._uncaught) return Result._uncaught(err); return Result.err(err); } /** * Call `fn` on the `val` if the result is ok. */ onValue(fn) { if (this.res.ok) try { fn(this.res.val); } catch (err) { return Result._uncaught(err); } return this; } /** * Call `fn` on the `err` if the result is err. */ onError(fn) { if (!this.res.ok) try { fn(this.res.err); } catch (err) { return Result._uncaught(err); } return this; } }; /** * This class is being used when `Result` methods encounter async code. * It isn't meant to be used directly, but exported for usage in type annotations. * * All the methods resemble `Result` methods, but work asynchronously. */ var AsyncResult = class AsyncResult { asyncResult; constructor(asyncResult) { this.asyncResult = asyncResult; } then(onfulfilled) { return this.asyncResult.then(onfulfilled); } static ok(val) { return new AsyncResult(Promise.resolve(Result.ok(val))); } static err(err) { return new AsyncResult(Promise.resolve(Result.err(err))); } static wrap(promise, onErr) { return new AsyncResult(promise.then((value) => { if (value instanceof Result) return value; if (isZodResult(value)) return fromZodResult(value); return Result.ok(value); }).catch((err) => { if (onErr) return onErr(err); return Result.err(err); })); } static wrapNullable(promise, errForNull, errForUndefined) { return new AsyncResult(promise.then((value) => fromNullable(value, errForNull, errForUndefined)).catch((err) => Result.err(err))); } /** * Returns a discriminated union for type-safe consumption of the result. * * ```ts * * const { val, err } = await Result.wrap(readFile('foo.txt')).unwrap(); * expect(val).toBe('foo'); * expect(err).toBeUndefined(); * * ``` */ unwrap() { return this.asyncResult.then((res) => res.unwrap()); } /** * Returns a success value or a fallback value. * * ```ts * * const val = await Result.wrap(readFile('foo.txt')).unwrapOr('bar'); * expect(val).toBe('bar'); * expect(err).toBeUndefined(); * * ``` */ unwrapOr(fallback) { return this.asyncResult.then((res) => res.unwrapOr(fallback)); } /** * Returns the ok-value or throw the error. */ async unwrapOrThrow() { return (await this.asyncResult).unwrapOrThrow(); } /** * Returns the ok-value or `null`. */ unwrapOrNull() { return this.asyncResult.then((res) => res.unwrapOrNull()); } transform(fn) { return new AsyncResult(this.asyncResult.then((oldResult) => { const { ok, val: value, err: error } = oldResult.unwrap(); if (!ok) return Result.err(error); try { const result = fn(value); if (result instanceof Result) return result; if (result instanceof AsyncResult) return result; if (isZodResult(result)) return fromZodResult(result); if (result instanceof Promise) return AsyncResult.wrap(result, (err) => { logger.warn({ err }, "AsyncResult: unhandled async transform error"); return Result._uncaught(err); }); return Result.ok(result); } catch (err) { logger.warn({ err }, "AsyncResult: unhandled transform error"); return Result._uncaught(err); } }).catch((err) => { return Result._uncaught(err); })); } catch(fn) { const caughtAsyncResult = this.asyncResult.then((result) => result.catch(fn)); return AsyncResult.wrap(caughtAsyncResult); } /** * Given a `schema`, returns a `Result` with `val` being the parsed value. * Additionally, `null` and `undefined` values are converted into Zod error. */ parse(schema) { return new AsyncResult(this.asyncResult.then((oldResult) => oldResult.parse(schema)).catch( /* istanbul ignore next: should never happen */ (err) => Result._uncaught(err) )); } onValue(fn) { return new AsyncResult(this.asyncResult.then((result) => result.onValue(fn)).catch( /* istanbul ignore next: should never happen */ (err) => Result._uncaught(err) )); } onError(fn) { return new AsyncResult(this.asyncResult.then((result) => result.onError(fn)).catch( /* istanbul ignore next: should never happen */ (err) => Result._uncaught(err) )); } }; //#endregion export { AsyncResult, Result }; //# sourceMappingURL=result.js.map