UNPKG

true-myth

Version:

A library for safe functional programming in JavaScript, with first-class support for TypeScript

441 lines (409 loc) 14.8 kB
"use strict"; /** {@include doc/result.md} @module */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Result = exports.isInstance = exports.safe = exports.ap = exports.equals = exports.match = exports.toJSON = exports.toString = exports.unwrapOrElse = exports.unwrapOr = exports.orElse = exports.or = exports.andThen = exports.and = exports.mapErr = exports.mapOrElse = exports.mapOr = exports.map = exports.tryOrElse = exports.err = exports.isErr = exports.isOk = exports.ok = exports.tryOr = exports.Variant = void 0; const unit_js_1 = require("./unit.cjs"); const utils_js_1 = require("./-private/utils.cjs"); /** Discriminant for {@linkcode Ok} and {@linkcode Err} variants of the {@linkcode Result} type. You can use the discriminant via the `variant` property of `Result` instances if you need to match explicitly on it. */ exports.Variant = { Ok: 'Ok', Err: 'Err', }; // Defines the *implementation*, but not the *types*. See the exports below. class ResultImpl { repr; constructor(repr) { this.repr = repr; } static ok(value) { // We produce `Unit` *only* in the case where no arguments are passed, so // that we can allow `undefined` in the cases where someone explicitly opts // into something like `Result<undefined, Blah>`. return arguments.length === 0 ? new ResultImpl(['Ok', unit_js_1.default]) : // SAFETY: TS does not understand that the arity check above accounts for // the case where the value is not passed. new ResultImpl(['Ok', value]); } static err(error) { // We produce `Unit` *only* in the case where no arguments are passed, so // that we can allow `undefined` in the cases where someone explicitly opts // into something like `Result<undefined, Blah>`. return arguments.length === 0 ? new ResultImpl(['Err', unit_js_1.default]) : // SAFETY: TS does not understand that the arity check above accounts for // the case where the value is not passed. new ResultImpl(['Err', error]); } /** Distinguish between the {@linkcode Variant.Ok} and {@linkcode Variant.Err} {@linkcode Variant variants}. */ get variant() { return this.repr[0]; } /** The wrapped value. @throws if you access when the {@linkcode Result} is not {@linkcode Ok} */ get value() { if (this.repr[0] === exports.Variant.Err) { throw new Error('Cannot get the value of Err'); } return this.repr[1]; } /** The wrapped error value. @throws if you access when the {@linkcode Result} is not {@linkcode Err} */ get error() { if (this.repr[0] === exports.Variant.Ok) { throw new Error('Cannot get the error of Ok'); } return this.repr[1]; } /** Is the {@linkcode Result} an {@linkcode Ok}? */ get isOk() { return this.repr[0] === exports.Variant.Ok; } /** Is the `Result` an `Err`? */ get isErr() { return this.repr[0] === exports.Variant.Err; } /** Method variant for {@linkcode map} */ map(mapFn) { return (this.repr[0] === 'Ok' ? exports.Result.ok(mapFn(this.repr[1])) : this); } /** Method variant for {@linkcode mapOr} */ mapOr(orU, mapFn) { return this.repr[0] === 'Ok' ? mapFn(this.repr[1]) : orU; } /** Method variant for {@linkcode mapOrElse} */ mapOrElse(orElseFn, mapFn) { return this.repr[0] === 'Ok' ? mapFn(this.repr[1]) : orElseFn(this.repr[1]); } /** Method variant for {@linkcode match} */ match(matcher) { return this.repr[0] === 'Ok' ? matcher.Ok(this.repr[1]) : matcher.Err(this.repr[1]); } /** Method variant for {@linkcode mapErr} */ mapErr(mapErrFn) { return (this.repr[0] === 'Ok' ? this : exports.Result.err(mapErrFn(this.repr[1]))); } /** Method variant for {@linkcode or} */ or(orResult) { return (this.repr[0] === 'Ok' ? this : orResult); } /** Method variant for {@linkcode orElse} */ orElse(orElseFn) { return (this.repr[0] === 'Ok' ? this : orElseFn(this.repr[1])); } /** Method variant for {@linkcode and} */ and(mAnd) { // (r.isOk ? andResult : err<U, E>(r.error)) return (this.repr[0] === 'Ok' ? mAnd : this); } /** Method variant for {@linkcode andThen} */ andThen(andThenFn) { return (this.repr[0] === 'Ok' ? andThenFn(this.repr[1]) : this); } /** Method variant for {@linkcode unwrapOr} */ unwrapOr(defaultValue) { return this.repr[0] === 'Ok' ? this.repr[1] : defaultValue; } /** Method variant for {@linkcode unwrapOrElse} */ unwrapOrElse(elseFn) { return this.repr[0] === 'Ok' ? this.repr[1] : elseFn(this.repr[1]); } /** Method variant for {@linkcode toString} */ toString() { return `${this.repr[0]}(${(0, utils_js_1.safeToString)(this.repr[1])})`; } /** Method variant for {@linkcode toJSON} */ toJSON() { const variant = this.repr[0]; return variant === 'Ok' ? { variant, value: this.repr[1] } : { variant, error: this.repr[1] }; } /** Method variant for {@linkcode equals} */ equals(comparison) { // SAFETY: these casts are stripping away the `Ok`/`Err` distinction and // simply testing what `comparison` *actually* is, which is always an // instance of `ResultImpl` (the same as this method itself). return (this.repr[0] === comparison.repr[0] && this.repr[1] === comparison.repr[1]); } /** Method variant for {@linkcode ap} */ ap(r) { return r.andThen((val) => this.map((fn) => fn(val))); } cast() { return this; } } function tryOr(error, callback) { const op = (cb) => { try { return (0, exports.ok)(cb()); } catch { return (0, exports.err)(error); } }; return (0, utils_js_1.curry1)(op, callback); } exports.tryOr = tryOr; /** Create an instance of {@linkcode Ok}. If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter: ```ts const yayNumber = Result.ok<number, string>(12); ``` Note: passing nothing, or passing `null` or `undefined` explicitly, will produce a `Result<Unit, E>`, rather than producing the nonsensical and in practice quite annoying `Result<null, string>` etc. See {@linkcode Unit} for more. ```ts const normalResult = Result.ok<number, string>(42); const explicitUnit = Result.ok<Unit, string>(Unit); const implicitUnit = Result.ok<Unit, string>(); ``` In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient. ```ts type SomeData = { //... }; const isValid = (data: SomeData): boolean => { // true or false... } const arrowValidate = (data: SomeData): Result<Unit, string> => isValid(data) ? Result.ok() : Result.err('something was wrong!'); function fnValidate(data: someData): Result<Unit, string> { return isValid(data) ? Result.ok() : Result.err('something was wrong'); } ``` @template T The type of the item contained in the `Result`. @param value The value to wrap in a `Result.Ok`. */ exports.ok = ResultImpl.ok; /** Is the {@linkcode Result} an {@linkcode Ok}? @template T The type of the item contained in the `Result`. @param result The `Result` to check. @returns A type guarded `Ok`. */ function isOk(result) { return result.isOk; } exports.isOk = isOk; /** Is the {@linkcode Result} an {@linkcode Err}? @template T The type of the item contained in the `Result`. @param result The `Result` to check. @returns A type guarded `Err`. */ function isErr(result) { return result.isErr; } exports.isErr = isErr; /** Create an instance of {@linkcode Err}. If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter: ```ts const notString = Result.err<number, string>('something went wrong'); ``` Note: passing nothing, or passing `null` or `undefined` explicitly, will produce a `Result<T, Unit>`, rather than producing the nonsensical and in practice quite annoying `Result<null, string>` etc. See {@linkcode Unit} for more. ```ts const normalResult = Result.err<number, string>('oh no'); const explicitUnit = Result.err<number, Unit>(Unit); const implicitUnit = Result.err<number, Unit>(); ``` In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient. ```ts type SomeData = { //... }; const isValid = (data: SomeData): boolean => { // true or false... } const arrowValidate = (data: SomeData): Result<number, Unit> => isValid(data) ? Result.ok(42) : Result.err(); function fnValidate(data: someData): Result<number, Unit> { return isValid(data) ? Result.ok(42) : Result.err(); } ``` @template T The type of the item contained in the `Result`. @param E The error value to wrap in a `Result.Err`. */ exports.err = ResultImpl.err; function tryOrElse(onError, callback) { const op = (cb) => { try { return (0, exports.ok)(cb()); } catch (e) { return (0, exports.err)(onError(e)); } }; return (0, utils_js_1.curry1)(op, callback); } exports.tryOrElse = tryOrElse; function map(mapFn, result) { const op = (r) => r.map(mapFn); return (0, utils_js_1.curry1)(op, result); } exports.map = map; function mapOr(orU, mapFn, result) { function fullOp(fn, r) { return r.mapOr(orU, fn); } function partialOp(fn, curriedResult) { return curriedResult !== undefined ? fullOp(fn, curriedResult) : (extraCurriedResult) => fullOp(fn, extraCurriedResult); } return mapFn === undefined ? partialOp : result === undefined ? partialOp(mapFn) : partialOp(mapFn, result); } exports.mapOr = mapOr; function mapOrElse(orElseFn, mapFn, result) { function fullOp(fn, r) { return r.mapOrElse(orElseFn, fn); } function partialOp(fn, curriedResult) { return curriedResult !== undefined ? fullOp(fn, curriedResult) : (extraCurriedResult) => fullOp(fn, extraCurriedResult); } return mapFn === undefined ? partialOp : result === undefined ? partialOp(mapFn) : partialOp(mapFn, result); } exports.mapOrElse = mapOrElse; function mapErr(mapErrFn, result) { const op = (r) => r.mapErr(mapErrFn); return (0, utils_js_1.curry1)(op, result); } exports.mapErr = mapErr; function and(andResult, result) { const op = (r) => r.and(andResult); return (0, utils_js_1.curry1)(op, result); } exports.and = and; function andThen(thenFn, result) { const op = (r) => r.andThen(thenFn); return (0, utils_js_1.curry1)(op, result); } exports.andThen = andThen; function or(defaultResult, result) { const op = (r) => r.or(defaultResult); return (0, utils_js_1.curry1)(op, result); } exports.or = or; function orElse(elseFn, result) { const op = (r) => r.orElse(elseFn); return (0, utils_js_1.curry1)(op, result); } exports.orElse = orElse; function unwrapOr(defaultValue, result) { const op = (r) => r.unwrapOr(defaultValue); return (0, utils_js_1.curry1)(op, result); } exports.unwrapOr = unwrapOr; function unwrapOrElse(orElseFn, result) { const op = (r) => r.unwrapOrElse(orElseFn); return (0, utils_js_1.curry1)(op, result); } exports.unwrapOrElse = unwrapOrElse; /** Create a `String` representation of a {@linkcode Result} instance. An {@linkcode Ok} instance will be `Ok(<representation of the value>)`, and an {@linkcode Err} instance will be `Err(<representation of the error>)`, where the representation of the value or error is simply the value or error's own `toString` representation. For example: call | output --------------------------------- | ---------------------- `toString(ok(42))` | `Ok(42)` `toString(ok([1, 2, 3]))` | `Ok(1,2,3)` `toString(ok({ an: 'object' }))` | `Ok([object Object])`n `toString(err(42))` | `Err(42)` `toString(err([1, 2, 3]))` | `Err(1,2,3)` `toString(err({ an: 'object' }))` | `Err([object Object])` @template T The type of the wrapped value; its own `.toString` will be used to print the interior contents of the `Just` variant. @param result The value to convert to a string. @returns The string representation of the `Maybe`. */ const toString = (result) => { return result.toString(); }; exports.toString = toString; /** * Create an `Object` representation of a {@linkcode Result} instance. * * Useful for serialization. `JSON.stringify()` uses it. * * @param result The value to convert to JSON * @returns The JSON representation of the `Result` */ const toJSON = (result) => { return result.toJSON(); }; exports.toJSON = toJSON; function match(matcher, result) { const op = (r) => r.mapOrElse(matcher.Err, matcher.Ok); return (0, utils_js_1.curry1)(op, result); } exports.match = match; function equals(resultB, resultA) { const op = (rA) => rA.equals(resultB); return (0, utils_js_1.curry1)(op, resultA); } exports.equals = equals; function ap(resultFn, result) { const op = (r) => resultFn.ap(r); return (0, utils_js_1.curry1)(op, result); } exports.ap = ap; function safe(fn, handleErr) { let errorHandler = handleErr ?? ((e) => e); return (...params) => tryOrElse(errorHandler, () => fn(...params)); } exports.safe = safe; /** Determine whether an item is an instance of {@linkcode Result}. @param item The item to check. */ function isInstance(item) { return item instanceof ResultImpl; } exports.isInstance = isInstance; /** A `Result` represents success ({@linkcode Ok}) or failure ({@linkcode Err}). The behavior of this type is checked by TypeScript at compile time, and bears no runtime overhead other than the very small cost of the container object. @class */ exports.Result = ResultImpl; exports.default = exports.Result; //# sourceMappingURL=result.cjs.map