@tempots/std
Version:
Std library for TypeScript. Natural complement to the Tempo libraries.
385 lines (384 loc) • 14 kB
JavaScript
const s = {
/**
* Creates a loading state.
* @param previousValue - The previous value.
* @returns A loading state.
* @public
*/
notAsked: { type: "NotAsked" },
/**
* Creates a loading state.
* @param previousValue - The previous value.
* @returns A loading state.
* @public
*/
loading(e = void 0) {
return { type: "Loading", previousValue: e };
},
/**
* Creates a successful state.
* @param value - The value.
* @returns A successful state.
* @public
*/
success(e) {
return { type: "AsyncSuccess", value: e };
},
/**
* Creates a failure state.
* @param error - The error.
* @returns A failure state.
* @public
*/
failure(e) {
return { type: "AsyncFailure", error: e };
},
/**
* Checks if the result is a success.
* @param r - The result.
* @returns `true` if the result is a success; otherwise, `false`.
* @public
*/
isSuccess(e) {
return e.type === "AsyncSuccess";
},
/**
* Checks if the result is a failure.
* @param r - The result.
* @returns `true` if the result is a failure; otherwise, `false`.
* @public
*/
isFailure(e) {
return e.type === "AsyncFailure";
},
/**
* Checks if the result is a not-asked.
* @param r - The result.
* @returns `true` if the result is not-asked; otherwise, `false`.
* @public
*/
isNotAsked(e) {
return e.type === "NotAsked";
},
/**
* Checks if the result is a loading.
* @param r - The result.
* @returns `true` if the result is loading; otherwise, `false`.
* @public
*/
isLoading(e) {
return e.type === "Loading";
},
/**
* Gets the value if the result is a success or loading with a previous value; otherwise, returns the alternative value.
* @param r - The result.
* @param alt - The alternative value.
* @returns The value if the result is a success or loading with previous value; otherwise, the alternative value.
* @public
*/
getOrElse(e, u) {
return s.isSuccess(e) ? e.value : s.isLoading(e) && e.previousValue !== void 0 ? e.previousValue : u;
},
/**
* Gets the value if the result is a success or loading with a previous value; otherwise, returns the value from the alternative function.
* @param r - The result.
* @param altf - The alternative function.
* @returns The value if the result is a success or loading with previous value; otherwise, the value from the alternative function.
* @public
*/
getOrElseLazy(e, u) {
return s.isSuccess(e) ? e.value : s.isLoading(e) && e.previousValue !== void 0 ? e.previousValue : u();
},
/**
* Gets the value if the result is a success or loading with a previous value; otherwise, returns `null`.
* @param r - The result.
* @returns The value if the result is a success or loading with previous value; otherwise, `null`.
* @public
*/
getOrNull(e) {
return s.isSuccess(e) ? e.value : s.isLoading(e) && e.previousValue !== void 0 ? e.previousValue : null;
},
/**
* Gets the value if the result is a success or loading with a previous value; otherwise, returns `undefined`.
* @param r - The result.
* @returns The value if the result is a success or loading with previous value; otherwise, `undefined`.
* @public
*/
getOrUndefined(e) {
if (s.isSuccess(e))
return e.value;
if (s.isLoading(e) && e.previousValue !== void 0)
return e.previousValue;
},
/**
* Gets the value of a `AsyncResult` if it is a `Success` or loading with a previous value, otherwise it throws the error contained in the `Failure`.
* @param r - The `AsyncResult` to get the value from.
* @returns The value of the `AsyncResult` if it is a `Success` or loading with previous value.
*/
getUnsafe: (e) => {
if (s.isSuccess(e))
return e.value;
if (s.isLoading(e) && e.previousValue !== void 0)
return e.previousValue;
throw s.isFailure(e) ? e.error : new Error(
"Cannot get value from a not-asked or loading result without previous value"
);
},
/**
* Based on the state of the result, it picks the appropriate function to call and returns the result.
* @param success - The function to call if the result is a success.
* @param failure - The function to call if the result is a failure.
* @param loading - The function to call if the result is loading.
* @param notAsked - The function to call if the result is not-asked.
* @returns The result of calling the appropriate function based on the state of the result.
* @public
*/
match: (e, {
success: u,
failure: r,
loading: i,
notAsked: a = i
}) => s.isSuccess(e) ? u(e.value) : s.isFailure(e) ? r(e.error) : s.isNotAsked(e) ? a() : i(e.previousValue),
/**
* Executes side effects based on the state of the result.
* Unlike `match`, all handlers are optional, allowing you to react only to specific states.
* The `else` handler is called when no specific handler is provided for the current state.
* @param r - The result.
* @param handlers - An object with optional handlers for each state and an optional `else` fallback.
* @returns The result that was passed in, allowing for chaining.
* @public
*/
effect: (e, u) => {
switch (e.type) {
case "AsyncSuccess":
u.success ? u.success(e.value) : u.else?.();
break;
case "AsyncFailure":
u.failure ? u.failure(e.error) : u.else?.();
break;
case "Loading":
u.loading ? u.loading(e.previousValue) : u.else?.();
break;
case "NotAsked":
u.notAsked ? u.notAsked() : u.else?.();
break;
}
return e;
},
/**
* When the result is a success, it applies the function to the value.
*
* @param r - The result.
* @param apply - The function to apply.
* @returns The result that was passed in.
* @public
*/
whenSuccess: (e, u) => (s.isSuccess(e) && u(e.value), e),
/**
* When the result is a failure, it applies the function to the error.
*
* @param r - The result.
* @param apply - The function to apply.
* @returns The result that was passed in.
* @public
*/
whenFailure: (e, u) => (s.isFailure(e) && u(e.error), e),
/**
* Compares two results for equality.
* @param r1 - The first result.
* @param r2 - The second result.
* @param options - The options to use for comparison. By default, uses strict equality.
* @returns `true` if the results are equal, `false` otherwise.
*/
equals: (e, u, r = {
valueEquals: (i, a) => i === a,
errorEquals: (i, a) => i === a
}) => e.type === "AsyncSuccess" && u.type === "AsyncSuccess" ? r.valueEquals(e.value, u.value) : e.type === "AsyncFailure" && u.type === "AsyncFailure" ? r.errorEquals(e.error, u.error) : e.type === "Loading" && u.type === "Loading" ? r.valueEquals(e.previousValue, u.previousValue) : e.type === "NotAsked" && u.type === "NotAsked",
/**
* Combines multiple results into a single result.
* @param results - The results to combine.
* @returns A single result that is a success if all the input results are successes, otherwise a failure.
*/
all: (e) => {
const u = [];
for (const r of e)
if (s.isSuccess(r))
u.push(r.value);
else
return r;
return s.success(u);
},
/**
* Converts a Promise to an AsyncResult.
* @param p - The Promise to convert.
* @returns A Promise that resolves to an AsyncResult.
*/
ofPromise: async (e) => {
try {
const u = await e;
return s.success(u);
} catch (u) {
return s.failure(u instanceof Error ? u : new Error(String(u)));
}
},
/**
* Maps the value of a successful `AsyncResult` to a new value using the provided function.
* For other states (NotAsked, Loading, Failure), the state is preserved.
* When mapping a Loading state with a previous value, the previous value is also mapped.
* @param result - The `AsyncResult` to map.
* @param fn - The mapping function to apply to the success value.
* @returns A new `AsyncResult` with the mapped value if successful, otherwise the original state.
* @public
*/
map: (e, u) => {
switch (e.type) {
case "AsyncSuccess":
return s.success(u(e.value));
case "NotAsked":
return s.notAsked;
case "AsyncFailure":
return s.failure(e.error);
case "Loading":
return s.loading(
e.previousValue != null ? u(e.previousValue) : void 0
);
}
},
/**
* Maps the value of a successful `AsyncResult` to a new `AsyncResult` using the provided function.
* This is useful for chaining operations that may also fail.
* @param result - The `AsyncResult` to flat map.
* @param fn - The mapping function that returns a new `AsyncResult`.
* @returns The result of the mapping function if successful, otherwise the original state.
* @public
*/
flatMap: (e, u) => {
switch (e.type) {
case "AsyncSuccess":
return u(e.value);
case "NotAsked":
return s.notAsked;
case "AsyncFailure":
return s.failure(e.error);
case "Loading":
return s.loading();
}
},
/**
* Maps the error of a failed `AsyncResult` to a new error using the provided function.
* For other states, the state is preserved.
* @param result - The `AsyncResult` to map the error of.
* @param fn - The mapping function to apply to the error.
* @returns A new `AsyncResult` with the mapped error if failed, otherwise the original state.
* @public
*/
mapError: (e, u) => {
switch (e.type) {
case "AsyncSuccess":
return s.success(e.value);
case "NotAsked":
return s.notAsked;
case "AsyncFailure":
return s.failure(u(e.error));
case "Loading":
return s.loading(e.previousValue);
}
},
/**
* Maps the error of a failed `AsyncResult` to a new `AsyncResult` using the provided function.
* This allows recovery from errors by returning a new successful result.
* @param result - The `AsyncResult` to recover from.
* @param fn - The recovery function that returns a new `AsyncResult`.
* @returns The result of the recovery function if failed, otherwise the original state.
* @public
*/
flatMapError: (e, u) => {
switch (e.type) {
case "AsyncSuccess":
return s.success(e.value);
case "NotAsked":
return s.notAsked;
case "AsyncFailure":
return u(e.error);
case "Loading":
return s.loading(e.previousValue);
}
},
/**
* Converts an `AsyncResult` to a `Result`, discarding the loading and not-asked states.
* Returns `undefined` if the result is not settled (i.e., NotAsked or Loading).
* @param result - The `AsyncResult` to convert.
* @returns A `Result` if the `AsyncResult` is settled, otherwise `undefined`.
* @public
*/
toResult: (e) => {
switch (e.type) {
case "AsyncSuccess":
return { type: "Success", value: e.value };
case "AsyncFailure":
return { type: "Failure", error: e.error };
case "NotAsked":
case "Loading":
return;
}
},
/**
* Checks if the result is settled (either success or failure).
* @param r - The result.
* @returns `true` if the result is settled; otherwise, `false`.
* @public
*/
isSettled(e) {
return e.type === "AsyncSuccess" || e.type === "AsyncFailure";
},
/**
* Recovers from a failure by providing an alternative value.
* @param result - The `AsyncResult` to recover from.
* @param fn - The function that provides an alternative value given the error.
* @returns A successful `AsyncResult` with the alternative value if failed, otherwise the original state.
* @public
*/
recover: (e, u) => {
switch (e.type) {
case "AsyncSuccess":
return s.success(e.value);
case "NotAsked":
return s.notAsked;
case "AsyncFailure":
return s.success(u(e.error));
case "Loading":
return s.loading(e.previousValue);
}
},
/**
* Applies a function wrapped in an `AsyncResult` to a value wrapped in an `AsyncResult`.
* Useful for applying multiple arguments to a function in a safe way.
* @param resultFn - The `AsyncResult` containing the function.
* @param resultVal - The `AsyncResult` containing the value.
* @returns A new `AsyncResult` with the result of applying the function to the value.
* @public
*/
ap: (e, u) => s.isSuccess(e) && s.isSuccess(u) ? s.success(e.value(u.value)) : s.isFailure(e) ? s.failure(e.error) : s.isFailure(u) ? s.failure(u.error) : s.isLoading(e) || s.isLoading(u) ? s.loading() : s.notAsked,
/**
* Maps two `AsyncResult` values using a function.
* @param r1 - The first `AsyncResult`.
* @param r2 - The second `AsyncResult`.
* @param fn - The function to apply to both values.
* @returns A new `AsyncResult` with the result of applying the function to both values.
* @public
*/
map2: (e, u, r) => s.isSuccess(e) && s.isSuccess(u) ? s.success(r(e.value, u.value)) : s.isFailure(e) ? s.failure(e.error) : s.isFailure(u) ? s.failure(u.error) : s.isLoading(e) || s.isLoading(u) ? s.loading() : s.notAsked,
/**
* Maps three `AsyncResult` values using a function.
* @param r1 - The first `AsyncResult`.
* @param r2 - The second `AsyncResult`.
* @param r3 - The third `AsyncResult`.
* @param fn - The function to apply to all three values.
* @returns A new `AsyncResult` with the result of applying the function to all three values.
* @public
*/
map3: (e, u, r, i) => s.isSuccess(e) && s.isSuccess(u) && s.isSuccess(r) ? s.success(i(e.value, u.value, r.value)) : s.isFailure(e) ? s.failure(e.error) : s.isFailure(u) ? s.failure(u.error) : s.isFailure(r) ? s.failure(r.error) : s.isLoading(e) || s.isLoading(u) || s.isLoading(r) ? s.loading() : s.notAsked
};
export {
s as AsyncResult
};