UNPKG

renovate

Version:

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

437 lines • 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AsyncResult = exports.Result = void 0; const zod_1 = require("zod"); const logger_1 = require("../logger"); 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 && input.error instanceof zod_1.ZodError; } } 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 === undefined) { 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 */ 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 { const result = input(); return fromNullable(result, 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) { // TODO: fix, should only allow `Error` type // eslint-disable-next-line @typescript-eslint/only-throw-error 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) { // TODO: fix, should only allow `Error` type // eslint-disable-next-line @typescript-eslint/only-throw-error throw this.res.err; } return fallback; } /** * Returns the ok-value or throw the error. */ unwrapOrThrow() { if (this.res.ok) { return this.res.val; } // TODO: fix, should only allow `Error` type // eslint-disable-next-line @typescript-eslint/only-throw-error 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) { // TODO: fix, should only allow `Error` type // eslint-disable-next-line @typescript-eslint/only-throw-error 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_1.logger.warn({ err }, 'Result: unhandled async transform error'); return Result._uncaught(err); }); } return Result.ok(result); } catch (err) { logger_1.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_1.logger.warn({ err }, 'Result: unexpected error in async catch handler'); return Result._uncaught(err); }); } return result; } catch (err) { logger_1.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) { const parseResult = schema .transform((result, ctx) => { if (result === undefined) { ctx.addIssue({ code: zod_1.ZodIssueCode.custom, message: `Result can't accept nullish values, but input was parsed by Zod schema to undefined`, }); return zod_1.NEVER; } if (result === null) { ctx.addIssue({ code: zod_1.ZodIssueCode.custom, message: `Result can't accept nullish values, but input was parsed by Zod schema to null`, }); return zod_1.NEVER; } return result; }) .safeParse(input); return fromZodResult(parseResult); } /** * 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; } } exports.Result = Result; /** * 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. */ 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) { // eslint-disable-next-line promise/no-promise-in-callback 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() { const result = await this.asyncResult; return result.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_1.logger.warn({ err }, 'AsyncResult: unhandled async transform error'); return Result._uncaught(err); }); } return Result.ok(result); } catch (err) { logger_1.logger.warn({ err }, 'AsyncResult: unhandled transform error'); return Result._uncaught(err); } }) .catch((err) => { // Happens when `.unwrap()` of `oldResult` throws return Result._uncaught(err); })); } catch(fn) { const caughtAsyncResult = this.asyncResult.then((result) => // eslint-disable-next-line promise/no-nesting 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))); } } exports.AsyncResult = AsyncResult; //# sourceMappingURL=result.js.map