veffect
Version:
powerful TypeScript validation library built on the robust foundation of Effect combining exceptional type safety, high performance, and developer experience. Taking inspiration from Effect's functional principles, VEffect delivers a balanced approach tha
478 lines • 14 kB
JavaScript
/**
* @since 2.0.0
*/
import * as Equivalence from "./Equivalence.js";
import { constNull, constUndefined, dual, identity } from "./Function.js";
import * as either from "./internal/either.js";
import { isFunction } from "./Predicate.js";
import * as Gen from "./Utils.js";
/**
* @category symbols
* @since 2.0.0
*/
export const TypeId = either.TypeId;
/**
* Constructs a new `Either` holding a `Right` value. This usually represents a successful value due to the right bias
* of this structure.
*
* @category constructors
* @since 2.0.0
*/
export const right = either.right;
/**
* Constructs a new `Either` holding a `Left` value. This usually represents a failure, due to the right-bias of this
* structure.
*
* @category constructors
* @since 2.0.0
*/
export const left = either.left;
/**
* Takes a lazy default and a nullable value, if the value is not nully (`null` or `undefined`), turn it into a `Right`, if the value is nully use
* the provided default as a `Left`.
*
* @example
* import * as Either from 'effect/Either'
*
* assert.deepStrictEqual(Either.fromNullable(1, () => 'fallback'), Either.right(1))
* assert.deepStrictEqual(Either.fromNullable(null, () => 'fallback'), Either.left('fallback'))
*
* @category constructors
* @since 2.0.0
*/
export const fromNullable = /*#__PURE__*/dual(2, (self, onNullable) => self == null ? left(onNullable(self)) : right(self));
/**
* @example
* import * as Either from 'effect/Either'
* import * as Option from 'effect/Option'
*
* assert.deepStrictEqual(Either.fromOption(Option.some(1), () => 'error'), Either.right(1))
* assert.deepStrictEqual(Either.fromOption(Option.none(), () => 'error'), Either.left('error'))
*
* @category constructors
* @since 2.0.0
*/
export const fromOption = either.fromOption;
const try_ = evaluate => {
if (isFunction(evaluate)) {
try {
return right(evaluate());
} catch (e) {
return left(e);
}
} else {
try {
return right(evaluate.try());
} catch (e) {
return left(evaluate.catch(e));
}
}
};
export {
/**
* Imports a synchronous side-effect into a pure `Either` value, translating any
* thrown exceptions into typed failed eithers creating with `Either.left`.
*
* @category constructors
* @since 2.0.0
*/
try_ as try };
/**
* Tests if a value is a `Either`.
*
* @param input - The value to test.
*
* @example
* import { isEither, left, right } from 'effect/Either'
*
* assert.deepStrictEqual(isEither(right(1)), true)
* assert.deepStrictEqual(isEither(left("a")), true)
* assert.deepStrictEqual(isEither({ right: 1 }), false)
*
* @category guards
* @since 2.0.0
*/
export const isEither = either.isEither;
/**
* Determine if a `Either` is a `Left`.
*
* @param self - The `Either` to check.
*
* @example
* import { isLeft, left, right } from 'effect/Either'
*
* assert.deepStrictEqual(isLeft(right(1)), false)
* assert.deepStrictEqual(isLeft(left("a")), true)
*
* @category guards
* @since 2.0.0
*/
export const isLeft = either.isLeft;
/**
* Determine if a `Either` is a `Right`.
*
* @param self - The `Either` to check.
*
* @example
* import { isRight, left, right } from 'effect/Either'
*
* assert.deepStrictEqual(isRight(right(1)), true)
* assert.deepStrictEqual(isRight(left("a")), false)
*
* @category guards
* @since 2.0.0
*/
export const isRight = either.isRight;
/**
* Converts a `Either` to an `Option` discarding the `Left`.
*
* Alias of {@link toOption}.
*
* @example
* import * as O from 'effect/Option'
* import * as E from 'effect/Either'
*
* assert.deepStrictEqual(E.getRight(E.right('ok')), O.some('ok'))
* assert.deepStrictEqual(E.getRight(E.left('err')), O.none())
*
* @category getters
* @since 2.0.0
*/
export const getRight = either.getRight;
/**
* Converts a `Either` to an `Option` discarding the value.
*
* @example
* import * as O from 'effect/Option'
* import * as E from 'effect/Either'
*
* assert.deepStrictEqual(E.getLeft(E.right('ok')), O.none())
* assert.deepStrictEqual(E.getLeft(E.left('err')), O.some('err'))
*
* @category getters
* @since 2.0.0
*/
export const getLeft = either.getLeft;
/**
* @category equivalence
* @since 2.0.0
*/
export const getEquivalence = (EquivalenceL, EquivalenceR) => Equivalence.make((x, y) => x === y || (isLeft(x) ? isLeft(y) && EquivalenceL(x.left, y.left) : isRight(y) && EquivalenceR(x.right, y.right)));
/**
* @category mapping
* @since 2.0.0
*/
export const mapBoth = /*#__PURE__*/dual(2, (self, {
onLeft,
onRight
}) => isLeft(self) ? left(onLeft(self.left)) : right(onRight(self.right)));
/**
* Maps the `Left` side of an `Either` value to a new `Either` value.
*
* @param self - The input `Either` value to map.
* @param f - A transformation function to apply to the `Left` value of the input `Either`.
*
* @category mapping
* @since 2.0.0
*/
export const mapLeft = /*#__PURE__*/dual(2, (self, f) => isLeft(self) ? left(f(self.left)) : right(self.right));
/**
* Maps the `Right` side of an `Either` value to a new `Either` value.
*
* @param self - An `Either` to map
* @param f - The function to map over the value of the `Either`
*
* @category mapping
* @since 2.0.0
*/
export const map = /*#__PURE__*/dual(2, (self, f) => isRight(self) ? right(f(self.right)) : left(self.left));
/**
* Takes two functions and an `Either` value, if the value is a `Left` the inner value is applied to the `onLeft function,
* if the value is a `Right` the inner value is applied to the `onRight` function.
*
* @example
* import * as E from 'effect/Either'
* import { pipe } from 'effect/Function'
*
* const onLeft = (strings: ReadonlyArray<string>): string => `strings: ${strings.join(', ')}`
*
* const onRight = (value: number): string => `Ok: ${value}`
*
* assert.deepStrictEqual(pipe(E.right(1), E.match({ onLeft, onRight })), 'Ok: 1')
* assert.deepStrictEqual(
* pipe(E.left(['string 1', 'string 2']), E.match({ onLeft, onRight })),
* 'strings: string 1, string 2'
* )
*
* @category pattern matching
* @since 2.0.0
*/
export const match = /*#__PURE__*/dual(2, (self, {
onLeft,
onRight
}) => isLeft(self) ? onLeft(self.left) : onRight(self.right));
/**
* Filter the right value with the provided function.
* If the predicate fails, set the left value with the result of the provided function.
*
* @example
* import * as E from 'effect/Either'
* import { pipe } from 'effect/Function'
*
* const isPositive = (n: number): boolean => n > 0
*
* assert.deepStrictEqual(
* pipe(
* E.right(1),
* E.filterOrLeft(isPositive, n => `${n} is not positive`)
* ),
* E.right(1)
* )
* assert.deepStrictEqual(
* pipe(
* E.right(0),
* E.filterOrLeft(isPositive, n => `${n} is not positive`)
* ),
* E.left("0 is not positive")
* )
*
* @since 2.0.0
* @category filtering & conditionals
*/
export const filterOrLeft = /*#__PURE__*/dual(3, (self, predicate, orLeftWith) => flatMap(self, r => predicate(r) ? right(r) : left(orLeftWith(r))));
/**
* @category getters
* @since 2.0.0
*/
export const merge = /*#__PURE__*/match({
onLeft: identity,
onRight: identity
});
/**
* Returns the wrapped value if it's a `Right` or a default value if is a `Left`.
*
* @example
* import * as Either from 'effect/Either'
*
* assert.deepStrictEqual(Either.getOrElse(Either.right(1), (error) => error + "!"), 1)
* assert.deepStrictEqual(Either.getOrElse(Either.left("not a number"), (error) => error + "!"), "not a number!")
*
* @category getters
* @since 2.0.0
*/
export const getOrElse = /*#__PURE__*/dual(2, (self, onLeft) => isLeft(self) ? onLeft(self.left) : self.right);
/**
* @example
* import * as Either from 'effect/Either'
*
* assert.deepStrictEqual(Either.getOrNull(Either.right(1)), 1)
* assert.deepStrictEqual(Either.getOrNull(Either.left("a")), null)
*
* @category getters
* @since 2.0.0
*/
export const getOrNull = /*#__PURE__*/getOrElse(constNull);
/**
* @example
* import * as Either from 'effect/Either'
*
* assert.deepStrictEqual(Either.getOrUndefined(Either.right(1)), 1)
* assert.deepStrictEqual(Either.getOrUndefined(Either.left("a")), undefined)
*
* @category getters
* @since 2.0.0
*/
export const getOrUndefined = /*#__PURE__*/getOrElse(constUndefined);
/**
* Extracts the value of an `Either` or throws if the `Either` is `Left`.
*
* If a default error is sufficient for your use case and you don't need to configure the thrown error, see {@link getOrThrow}.
*
* @param self - The `Either` to extract the value from.
* @param onLeft - A function that will be called if the `Either` is `Left`. It returns the error to be thrown.
*
* @example
* import * as E from "effect/Either"
*
* assert.deepStrictEqual(
* E.getOrThrowWith(E.right(1), () => new Error('Unexpected Left')),
* 1
* )
* assert.throws(() => E.getOrThrowWith(E.left("error"), () => new Error('Unexpected Left')))
*
* @category getters
* @since 2.0.0
*/
export const getOrThrowWith = /*#__PURE__*/dual(2, (self, onLeft) => {
if (isRight(self)) {
return self.right;
}
throw onLeft(self.left);
});
/**
* Extracts the value of an `Either` or throws if the `Either` is `Left`.
*
* The thrown error is a default error. To configure the error thrown, see {@link getOrThrowWith}.
*
* @param self - The `Either` to extract the value from.
* @throws `Error("getOrThrow called on a Left")`
*
* @example
* import * as E from "effect/Either"
*
* assert.deepStrictEqual(E.getOrThrow(E.right(1)), 1)
* assert.throws(() => E.getOrThrow(E.left("error")))
*
* @category getters
* @since 2.0.0
*/
export const getOrThrow = /*#__PURE__*/getOrThrowWith(() => new Error("getOrThrow called on a Left"));
/**
* Returns `self` if it is a `Right` or `that` otherwise.
*
* @param self - The input `Either` value to check and potentially return.
* @param that - A function that takes the error value from `self` (if it's a `Left`) and returns a new `Either` value.
*
* @category error handling
* @since 2.0.0
*/
export const orElse = /*#__PURE__*/dual(2, (self, that) => isLeft(self) ? that(self.left) : right(self.right));
/**
* @category sequencing
* @since 2.0.0
*/
export const flatMap = /*#__PURE__*/dual(2, (self, f) => isLeft(self) ? left(self.left) : f(self.right));
/**
* Executes a sequence of two `Either`s. The second `Either` can be dependent on the result of the first `Either`.
*
* @category sequencing
* @since 2.0.0
*/
export const andThen = /*#__PURE__*/dual(2, (self, f) => flatMap(self, a => {
const b = isFunction(f) ? f(a) : f;
return isEither(b) ? b : right(b);
}));
/**
* @category zipping
* @since 2.0.0
*/
export const zipWith = /*#__PURE__*/dual(3, (self, that, f) => flatMap(self, r => map(that, r2 => f(r, r2))));
/**
* @category combining
* @since 2.0.0
*/
export const ap = /*#__PURE__*/dual(2, (self, that) => zipWith(self, that, (f, a) => f(a)));
/**
* Takes a structure of `Either`s and returns an `Either` of values with the same structure.
*
* - If a tuple is supplied, then the returned `Either` will contain a tuple with the same length.
* - If a struct is supplied, then the returned `Either` will contain a struct with the same keys.
* - If an iterable is supplied, then the returned `Either` will contain an array.
*
* @param fields - the struct of `Either`s to be sequenced.
*
* @example
* import * as Either from "effect/Either"
*
* assert.deepStrictEqual(Either.all([Either.right(1), Either.right(2)]), Either.right([1, 2]))
* assert.deepStrictEqual(Either.all({ right: Either.right(1), b: Either.right("hello") }), Either.right({ right: 1, b: "hello" }))
* assert.deepStrictEqual(Either.all({ right: Either.right(1), b: Either.left("error") }), Either.left("error"))
*
* @category combining
* @since 2.0.0
*/
// @ts-expect-error
export const all = input => {
if (Symbol.iterator in input) {
const out = [];
for (const e of input) {
if (isLeft(e)) {
return e;
}
out.push(e.right);
}
return right(out);
}
const out = {};
for (const key of Object.keys(input)) {
const e = input[key];
if (isLeft(e)) {
return e;
}
out[key] = e.right;
}
return right(out);
};
/**
* Returns an `Either` that swaps the error/success cases. This allows you to
* use all methods on the error channel, possibly before flipping back.
*
* @since 2.0.0
* @category mapping
*/
export const flip = self => isLeft(self) ? right(self.left) : left(self.right);
const adapter = /*#__PURE__*/Gen.adapter();
/**
* @category generators
* @since 2.0.0
*/
export const gen = f => {
const iterator = f(adapter);
let state = iterator.next();
if (state.done) {
return right(state.value);
} else {
let current = state.value.value;
if (isLeft(current)) {
return current;
}
while (!state.done) {
state = iterator.next(current.right);
if (!state.done) {
current = state.value.value;
if (isLeft(current)) {
return current;
}
}
}
return right(state.value);
}
};
// -------------------------------------------------------------------------------------
// do notation
// -------------------------------------------------------------------------------------
/**
* @since 2.4.0
* @category do notation
*/
export const Do = /*#__PURE__*/right({});
/**
* Binds an effectful value in a `do` scope
*
* @since 2.4.0
* @category do notation
*/
export const bind = /*#__PURE__*/dual(3, (self, tag, f) => flatMap(self, k => map(f(k), a => ({
...k,
[tag]: a
}))));
/**
* @category do notation
* @since 2.4.0
*/
export const bindTo = /*#__PURE__*/dual(2, (self, tag) => map(self, a => ({
[tag]: a
})));
const let_ = /*#__PURE__*/dual(3, (self, tag, f) => map(self, k => ({
...k,
[tag]: f(k)
})));
export {
/**
* Like bind for values
*
* @since 2.4.0
* @category do notation
*/
let_ as let };
//# sourceMappingURL=Either.js.map