renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
333 lines (332 loc) • 9.47 kB
JavaScript
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