UNPKG

@tempots/std

Version:

Std library for TypeScript. Natural complement to the Tempo libraries.

385 lines (384 loc) 14 kB
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 };