| /** | |
| * Represents observable state for asynchronous values. | |
| * | |
| * `AsyncResult<A, E>` records whether asynchronous work has no value yet, | |
| * succeeded with an `A`, or failed with an `E`. Every state also carries a | |
| * `waiting` flag, so callers can keep showing the current value while newer | |
| * work is loading, refreshing, retrying, or recovering. This module includes | |
| * constructors, checks, accessors, mapping and matching helpers, ways to combine | |
| * several results, and schemas for encoding or decoding results. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Cause from "../../Cause.ts" | |
| import * as Effect from "../../Effect.ts" | |
| import * as Equal from "../../Equal.ts" | |
| import * as Exit from "../../Exit.ts" | |
| import type { LazyArg } from "../../Function.ts" | |
| import { constTrue, dual, identity } from "../../Function.ts" | |
| import * as Hash from "../../Hash.ts" | |
| import * as Option from "../../Option.ts" | |
| import { type Pipeable, pipeArguments } from "../../Pipeable.ts" | |
| import type { Predicate, Refinement } from "../../Predicate.ts" | |
| import { hasProperty, isIterable } from "../../Predicate.ts" | |
| import * as Result from "../../Result.ts" | |
| import * as Schema_ from "../../Schema.ts" | |
| import * as SchemaIssue from "../../SchemaIssue.ts" | |
| import * as SchemaParser from "../../SchemaParser.ts" | |
| import * as SchemaTransformation from "../../SchemaTransformation.ts" | |
| import type * as Types from "../../Types.ts" | |
| /** | |
| * Type-level identifier used to recognize `AsyncResult` values. | |
| * | |
| * @category type IDs | |
| * @since 4.0.0 | |
| */ | |
| export type TypeId = "~effect/reactivity/AsyncResult" | |
| /** | |
| * Runtime identifier attached to `AsyncResult` values and used by `isAsyncResult`. | |
| * | |
| * @category type IDs | |
| * @since 4.0.0 | |
| */ | |
| export const TypeId: TypeId = "~effect/reactivity/AsyncResult" | |
| /** | |
| * Represents the state of an asynchronous value as `Initial`, `Success`, or `Failure`, with a `waiting` flag for in-flight refreshes. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export type AsyncResult<A, E = never> = Initial<A, E> | Success<A, E> | Failure<A, E> | |
| /** | |
| * Returns `true` when a value is an `AsyncResult`. | |
| * | |
| * @category guards | |
| * @since 4.0.0 | |
| */ | |
| export const isAsyncResult = (u: unknown): u is AsyncResult<unknown, unknown> => hasProperty(u, TypeId) | |
| /** | |
| * Namespace containing type-level helpers and the shared prototype shape for `AsyncResult` values. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| export declare namespace AsyncResult { | |
| /** | |
| * Common prototype fields implemented by every `AsyncResult` variant, including pipeability, the type marker, phantom type members, and the `waiting` flag. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface Proto<A, E> extends Pipeable { | |
| readonly [TypeId]: { | |
| readonly E: (_: never) => E | |
| readonly A: (_: never) => A | |
| } | |
| readonly waiting: boolean | |
| } | |
| /** | |
| * Extracts the success value type from an `AsyncResult`. | |
| * | |
| * @category utility types | |
| * @since 4.0.0 | |
| */ | |
| export type Success<R> = R extends AsyncResult<infer A, infer _> ? A : never | |
| /** | |
| * Extracts the failure error type from an `AsyncResult`. | |
| * | |
| * @category utility types | |
| * @since 4.0.0 | |
| */ | |
| export type Failure<R> = R extends AsyncResult<infer _, infer E> ? E : never | |
| } | |
| /** | |
| * Rebuilds an `AsyncResult` with new success and failure types while preserving the variant of another result. | |
| * | |
| * @category utility types | |
| * @since 4.0.0 | |
| */ | |
| export type With<R extends AsyncResult<any, any>, A, E> = R extends Initial<infer _A, infer _E> ? Initial<A, E> | |
| : R extends Success<infer _A, infer _E> ? Success<A, E> | |
| : R extends Failure<infer _A, infer _E> ? Failure<A, E> | |
| : never | |
| const ResultProto = { | |
| [TypeId]: { | |
| E: identity, | |
| A: identity | |
| }, | |
| pipe() { | |
| return pipeArguments(this, arguments) | |
| }, | |
| [Equal.symbol](this: AsyncResult<any, any>, that: AsyncResult<any, any>): boolean { | |
| if (this._tag !== that._tag || this.waiting !== that.waiting) { | |
| return false | |
| } | |
| switch (this._tag) { | |
| case "Initial": | |
| return true | |
| case "Success": | |
| return Equal.equals(this.value, (that as Success<any, any>).value) | |
| case "Failure": | |
| return Equal.equals(this.cause, (that as Failure<any, any>).cause) | |
| } | |
| }, | |
| [Hash.symbol](this: AsyncResult<any, any>): number { | |
| const tagHash = Hash.string(`${this._tag}:${this.waiting}`) | |
| if (this._tag === "Initial") { | |
| return tagHash | |
| } | |
| return Hash.combine(tagHash)(this._tag === "Success" ? Hash.hash(this.value) : Hash.hash(this.cause)) | |
| } | |
| } | |
| /** | |
| * Returns whether an `AsyncResult` is currently waiting for an asynchronous computation or refresh to finish. | |
| * | |
| * @category refinements | |
| * @since 4.0.0 | |
| */ | |
| export const isWaiting = <A, E>(result: AsyncResult<A, E>): boolean => result.waiting | |
| /** | |
| * Initial `AsyncResult` state before a success value or failure cause is available. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface Initial<A, E = never> extends AsyncResult.Proto<A, E> { | |
| readonly _tag: "Initial" | |
| } | |
| /** | |
| * Converts an `Exit` into a `Success` when it succeeds or a `Failure` carrying the exit cause when it fails. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const fromExit = <A, E>(exit: Exit.Exit<A, E>): Success<A, E> | Failure<A, E> => | |
| exit._tag === "Success" ? success(exit.value) : failure(exit.cause) | |
| /** | |
| * Converts an `Exit` to a result, preserving the latest previous success when the exit is a failure. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const fromExitWithPrevious = <A, E>( | |
| exit: Exit.Exit<A, E>, | |
| previous: Option.Option<AsyncResult<A, E>> | |
| ): Success<A, E> | Failure<A, E> => | |
| exit._tag === "Success" ? success(exit.value) : failureWithPrevious(exit.cause, { previous }) | |
| /** | |
| * Creates a waiting result from an optional previous result, using `Initial(true)` when no previous result exists. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const waitingFrom = <A, E>(previous: Option.Option<AsyncResult<A, E>>): AsyncResult<A, E> => { | |
| if (previous._tag === "None") { | |
| return initial(true) | |
| } | |
| return waiting(previous.value) | |
| } | |
| /** | |
| * Returns `true` when an `AsyncResult` is in the `Initial` state. | |
| * | |
| * @category refinements | |
| * @since 4.0.0 | |
| */ | |
| export const isInitial = <A, E>(result: AsyncResult<A, E>): result is Initial<A, E> => result._tag === "Initial" | |
| /** | |
| * Returns `true` when an `AsyncResult` is either `Success` or `Failure`. | |
| * | |
| * @category refinements | |
| * @since 4.0.0 | |
| */ | |
| export const isNotInitial = <A, E>(result: AsyncResult<A, E>): result is Success<A, E> | Failure<A, E> => | |
| result._tag !== "Initial" | |
| /** | |
| * Creates an `Initial` result, optionally marking it as waiting. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const initial = <A = never, E = never>(waiting = false): Initial<A, E> => { | |
| const result = Object.create(ResultProto) | |
| result._tag = "Initial" | |
| result.waiting = waiting | |
| return result | |
| } | |
| /** | |
| * Successful `AsyncResult` containing the current value, its timestamp, and the shared waiting flag. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface Success<A, E = never> extends AsyncResult.Proto<A, E> { | |
| readonly _tag: "Success" | |
| readonly value: A | |
| readonly timestamp: number | |
| } | |
| /** | |
| * Returns `true` when an `AsyncResult` is a `Success`. | |
| * | |
| * @category refinements | |
| * @since 4.0.0 | |
| */ | |
| export const isSuccess = <A, E>(result: AsyncResult<A, E>): result is Success<A, E> => result._tag === "Success" | |
| /** | |
| * Creates a `Success` result with a value and optional `waiting` flag or timestamp override. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const success = <A, E = never>(value: A, options?: { | |
| readonly waiting?: boolean | undefined | |
| readonly timestamp?: number | undefined | |
| }): Success<A, E> => { | |
| const result = Object.create(ResultProto) | |
| result._tag = "Success" | |
| result.value = value | |
| result.waiting = options?.waiting ?? false | |
| result.timestamp = options?.timestamp ?? Date.now() | |
| return result | |
| } | |
| /** | |
| * Failed `AsyncResult` containing a failure cause and the latest previous success when one is available. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface Failure<A, E = never> extends AsyncResult.Proto<A, E> { | |
| readonly _tag: "Failure" | |
| readonly cause: Cause.Cause<E> | |
| readonly previousSuccess: Option.Option<Success<A, E>> | |
| } | |
| /** | |
| * Returns `true` when an `AsyncResult` is a `Failure`. | |
| * | |
| * @category refinements | |
| * @since 4.0.0 | |
| */ | |
| export const isFailure = <A, E>(result: AsyncResult<A, E>): result is Failure<A, E> => result._tag === "Failure" | |
| /** | |
| * Returns `true` when an `AsyncResult` is a `Failure` whose cause contains only interruptions. | |
| * | |
| * @category refinements | |
| * @since 4.0.0 | |
| */ | |
| export const isInterrupted = <A, E>(result: AsyncResult<A, E>): result is Failure<A, E> => | |
| result._tag === "Failure" && Cause.hasInterruptsOnly(result.cause) | |
| /** | |
| * Creates a `Failure` result from a `Cause`, optionally preserving a previous success and marking the result as waiting. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const failure = <A, E = never>( | |
| cause: Cause.Cause<E>, | |
| options?: { | |
| readonly previousSuccess?: Option.Option<Success<A, E>> | undefined | |
| readonly waiting?: boolean | undefined | |
| } | |
| ): Failure<A, E> => { | |
| const result = Object.create(ResultProto) | |
| result._tag = "Failure" | |
| result.cause = cause | |
| result.previousSuccess = options?.previousSuccess ?? Option.none() | |
| result.waiting = options?.waiting ?? false | |
| return result | |
| } | |
| /** | |
| * Creates a `Failure` result from a `Cause`, carrying forward the latest success stored in a previous result. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const failureWithPrevious = <A, E>( | |
| cause: Cause.Cause<E>, | |
| options: { | |
| readonly previous: Option.Option<AsyncResult<A, E>> | |
| readonly waiting?: boolean | undefined | |
| } | |
| ): Failure<A, E> => | |
| failure(cause, { | |
| previousSuccess: Option.flatMap(options.previous, (result) => | |
| isSuccess(result) | |
| ? Option.some(result) | |
| : isFailure(result) | |
| ? result.previousSuccess | |
| : Option.none()), | |
| waiting: options.waiting | |
| }) | |
| /** | |
| * Creates a `Failure` result from a typed error, wrapping it in `Cause.fail`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const fail = <E, A = never>(error: E, options?: { | |
| readonly previousSuccess?: Option.Option<Success<A, E>> | undefined | |
| readonly waiting?: boolean | undefined | |
| }): Failure<A, E> => failure(Cause.fail(error), options) | |
| /** | |
| * Creates a `Failure` result from a typed error while carrying forward the latest success stored in a previous result. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const failWithPrevious = <A, E>( | |
| error: E, | |
| options: { | |
| readonly previous: Option.Option<AsyncResult<A, E>> | |
| readonly waiting?: boolean | undefined | |
| } | |
| ): Failure<A, E> => failureWithPrevious(Cause.fail(error), options) | |
| /** | |
| * Marks an `AsyncResult` as waiting, optionally touching the timestamp when the result is a `Success`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const waiting = <R extends AsyncResult<any, any>>(self: R, options?: { | |
| readonly touch?: boolean | undefined | |
| }): R => { | |
| if (self.waiting) { | |
| return options?.touch ? touch(self) : self | |
| } | |
| const result = Object.assign(Object.create(ResultProto), self) | |
| result.waiting = true | |
| if (options?.touch && isSuccess(result)) { | |
| ;(result as any).timestamp = Date.now() | |
| } | |
| return result | |
| } | |
| /** | |
| * Refreshes the timestamp of a `Success` result while preserving its value and waiting flag; non-success results are returned unchanged. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const touch = <A extends AsyncResult<any, any>>(result: A): A => { | |
| if (isSuccess(result)) { | |
| return success(result.value, { waiting: result.waiting }) as A | |
| } | |
| return result | |
| } | |
| /** | |
| * Replaces a `Failure` value's stored previous success with the latest success | |
| * found in another result. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const replacePrevious = <R extends AsyncResult<any, any>, XE, A>( | |
| self: R, | |
| previous: Option.Option<AsyncResult<A, XE>> | |
| ): With<R, A, AsyncResult.Failure<R>> => { | |
| if (self._tag === "Failure") { | |
| return failureWithPrevious(self.cause, { previous, waiting: self.waiting }) as any | |
| } | |
| return self as any | |
| } | |
| /** | |
| * Returns the current success value, or the previous success value stored in a failure, as an `Option`. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const value = <A, E>(self: AsyncResult<A, E>): Option.Option<A> => { | |
| if (self._tag === "Success") { | |
| return Option.some(self.value) | |
| } else if (self._tag === "Failure") { | |
| return Option.map(self.previousSuccess, (s) => s.value) | |
| } | |
| return Option.none() | |
| } | |
| /** | |
| * Returns the available value from `value`, or evaluates the fallback when no current or previous success exists. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const getOrElse: { | |
| /** | |
| * Returns the available value from `value`, or evaluates the fallback when no current or previous success exists. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| <B>(orElse: LazyArg<B>): <A, E>(self: AsyncResult<A, E>) => A | B | |
| /** | |
| * Returns the available value from `value`, or evaluates the fallback when no current or previous success exists. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| <A, E, B>(self: AsyncResult<A, E>, orElse: LazyArg<B>): A | B | |
| } = dual(2, <A, E, B>(self: AsyncResult<A, E>, orElse: LazyArg<B>): A | B => Option.getOrElse(value(self), orElse)) | |
| /** | |
| * Returns the available value from `value`, or throws `NoSuchElementError` when no current or previous success exists. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const getOrThrow = <A, E>(self: AsyncResult<A, E>): A => | |
| Option.getOrThrowWith(value(self), () => new Cause.NoSuchElementError("AsyncResult.getOrThrow: no value found")) | |
| /** | |
| * Returns the failure cause when the result is a `Failure`, otherwise `None`. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const cause = <A, E>(self: AsyncResult<A, E>): Option.Option<Cause.Cause<E>> => | |
| self._tag === "Failure" ? Option.some(self.cause) : Option.none() | |
| /** | |
| * Returns the first typed error from a failure cause, or `None` for successes, initial results, defects, and interrupt-only causes. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const error = <A, E>(self: AsyncResult<A, E>): Option.Option<E> => | |
| self._tag === "Failure" ? Cause.findErrorOption(self.cause) : Option.none() | |
| /** | |
| * Converts a result to an `Exit`, succeeding with a success value, failing with a failure cause, or failing with `NoSuchElementError` for `Initial`. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const toExit = <A, E>( | |
| self: AsyncResult<A, E> | |
| ): Exit.Exit<A, E | Cause.NoSuchElementError> => { | |
| switch (self._tag) { | |
| case "Success": { | |
| return Exit.succeed(self.value) | |
| } | |
| case "Failure": { | |
| return Exit.failCause(self.cause) | |
| } | |
| default: { | |
| return Exit.fail(new Cause.NoSuchElementError()) | |
| } | |
| } | |
| } | |
| /** | |
| * Maps the success value of an `AsyncResult`, also mapping any previous success stored in a failure while leaving initial results unchanged. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const map: { | |
| /** | |
| * Maps the success value of an `AsyncResult`, also mapping any previous success stored in a failure while leaving initial results unchanged. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A, B>(f: (a: A) => B): <E>(self: AsyncResult<A, E>) => AsyncResult<B, E> | |
| /** | |
| * Maps the success value of an `AsyncResult`, also mapping any previous success stored in a failure while leaving initial results unchanged. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <E, A, B>(self: AsyncResult<A, E>, f: (a: A) => B): AsyncResult<B, E> | |
| } = dual(2, <E, A, B>(self: AsyncResult<A, E>, f: (a: A) => B): AsyncResult<B, E> => { | |
| switch (self._tag) { | |
| case "Initial": | |
| return self as any as AsyncResult<B, E> | |
| case "Failure": | |
| return failure(self.cause, { | |
| previousSuccess: Option.map(self.previousSuccess, (s) => success(f(s.value), s)), | |
| waiting: self.waiting | |
| }) | |
| case "Success": | |
| return success(f(self.value), self) | |
| } | |
| }) | |
| /** | |
| * Maps the success value of an `AsyncResult` and flattens the result. | |
| * | |
| * **When to use** | |
| * | |
| * Use to sequence computations that may return another `AsyncResult` while | |
| * preserving initial and failure states. | |
| * | |
| * **Details** | |
| * | |
| * Initial results are left unchanged. Failures preserve their cause and remap | |
| * the stored previous success when the mapping function returns a success. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const flatMap: { | |
| /** | |
| * Maps the success value of an `AsyncResult` and flattens the result. | |
| * | |
| * **When to use** | |
| * | |
| * Use to sequence computations that may return another `AsyncResult` while | |
| * preserving initial and failure states. | |
| * | |
| * **Details** | |
| * | |
| * Initial results are left unchanged. Failures preserve their cause and remap | |
| * the stored previous success when the mapping function returns a success. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A, E, B, E2>(f: (a: A, prev: Success<A, E>) => AsyncResult<A, E2>): (self: AsyncResult<A, E>) => AsyncResult<B, E | E2> | |
| /** | |
| * Maps the success value of an `AsyncResult` and flattens the result. | |
| * | |
| * **When to use** | |
| * | |
| * Use to sequence computations that may return another `AsyncResult` while | |
| * preserving initial and failure states. | |
| * | |
| * **Details** | |
| * | |
| * Initial results are left unchanged. Failures preserve their cause and remap | |
| * the stored previous success when the mapping function returns a success. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <E, A, B, E2>( | |
| self: AsyncResult<A, E>, | |
| f: (a: A, prev: Success<A, E>) => AsyncResult<B, E2> | |
| ): AsyncResult<B, E | E2> | |
| } = dual( | |
| 2, | |
| <E, A, B, E2>( | |
| self: AsyncResult<A, E>, | |
| f: (a: A, prev: Success<A, E>) => AsyncResult<B, E2> | |
| ): AsyncResult<B, E | E2> => { | |
| switch (self._tag) { | |
| case "Initial": | |
| return self as any as AsyncResult<B, E> | |
| case "Failure": | |
| return failure</** | |
| * Maps the success value of an `AsyncResult` and flattens the result. | |
| * | |
| * **When to use** | |
| * | |
| * Use to sequence computations that may return another `AsyncResult` while | |
| * preserving initial and failure states. | |
| * | |
| * **Details** | |
| * | |
| * Initial results are left unchanged. Failures preserve their cause and remap | |
| * the stored previous success when the mapping function returns a success. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| B, /** | |
| * Maps the success value of an `AsyncResult` and flattens the result. | |
| * | |
| * **When to use** | |
| * | |
| * Use to sequence computations that may return another `AsyncResult` while | |
| * preserving initial and failure states. | |
| * | |
| * **Details** | |
| * | |
| * Initial results are left unchanged. Failures preserve their cause and remap | |
| * the stored previous success when the mapping function returns a success. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| E | E2>(self.cause, { | |
| previousSuccess: Option.flatMap(self.previousSuccess, (s) => { | |
| const next = f(s.value, s) | |
| return isSuccess(next) ? Option.some(next) : Option.none() | |
| }), | |
| waiting: self.waiting | |
| }); | |
| case "Success": | |
| return f(self.value, self) | |
| } | |
| } | |
| ) | |
| /** | |
| * Pattern matches an `AsyncResult` by calling the handler for `Initial`, `Failure`, or `Success`. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const match: { | |
| /** | |
| * Pattern matches an `AsyncResult` by calling the handler for `Initial`, `Failure`, or `Success`. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A, E, X, Y, Z>( | |
| options: { | |
| readonly onInitial: (_: Initial<A, E>) => X | |
| readonly onFailure: (_: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| } | |
| ): (self: AsyncResult<A, E>) => X | Y | Z | |
| /** | |
| * Pattern matches an `AsyncResult` by calling the handler for `Initial`, `Failure`, or `Success`. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A, E, X, Y, Z>( | |
| self: AsyncResult<A, E>, | |
| options: { | |
| readonly onInitial: (_: Initial<A, E>) => X | |
| readonly onFailure: (_: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| } | |
| ): X | Y | Z | |
| } = dual(2, <A, E, X, Y, Z>(self: AsyncResult<A, E>, options: { | |
| readonly onInitial: (_: Initial<A, E>) => X | |
| readonly onFailure: (_: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| }): X | Y | Z => { | |
| switch (self._tag) { | |
| case "Initial": | |
| return options.onInitial(self) | |
| case "Failure": | |
| return options.onFailure(self) | |
| case "Success": | |
| return options.onSuccess(self) | |
| } | |
| }) | |
| /** | |
| * Pattern matches a result, handling successes and initials directly while splitting failures into typed errors or squashed non-error causes passed to `onDefect`. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const matchWithError: { | |
| /** | |
| * Pattern matches a result, handling successes and initials directly while splitting failures into typed errors or squashed non-error causes passed to `onDefect`. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A, E, W, X, Y, Z>( | |
| options: { | |
| readonly onInitial: (_: Initial<A, E>) => W | |
| readonly onError: (error: E, _: Failure<A, E>) => X | |
| readonly onDefect: (defect: unknown, _: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| } | |
| ): (self: AsyncResult<A, E>) => W | X | Y | Z | |
| /** | |
| * Pattern matches a result, handling successes and initials directly while splitting failures into typed errors or squashed non-error causes passed to `onDefect`. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A, E, W, X, Y, Z>( | |
| self: AsyncResult<A, E>, | |
| options: { | |
| readonly onInitial: (_: Initial<A, E>) => W | |
| readonly onError: (error: E, _: Failure<A, E>) => X | |
| readonly onDefect: (defect: unknown, _: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| } | |
| ): W | X | Y | Z | |
| } = dual(2, <A, E, W, X, Y, Z>(self: AsyncResult<A, E>, options: { | |
| readonly onInitial: (_: Initial<A, E>) => W | |
| readonly onError: (error: E, _: Failure<A, E>) => X | |
| readonly onDefect: (defect: unknown, _: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| }): W | X | Y | Z => { | |
| switch (self._tag) { | |
| case "Initial": | |
| return options.onInitial(self) | |
| case "Failure": { | |
| const result = Cause.findError(self.cause) | |
| if (Result.isFailure(result)) { | |
| return options.onDefect(Cause.squash(result.failure), self) | |
| } | |
| return options.onError(result.success, self) | |
| } | |
| case "Success": | |
| return options.onSuccess(self) | |
| } | |
| }) | |
| /** | |
| * Pattern matches a result by calling `onWaiting` for waiting or initial states, otherwise handling successes and splitting failures into typed errors or squashed non-error causes. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const matchWithWaiting: { | |
| /** | |
| * Pattern matches a result by calling `onWaiting` for waiting or initial states, otherwise handling successes and splitting failures into typed errors or squashed non-error causes. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A, E, W, X, Y, Z>( | |
| options: { | |
| readonly onWaiting: (_: AsyncResult<A, E>) => W | |
| readonly onError: (error: E, _: Failure<A, E>) => X | |
| readonly onDefect: (defect: unknown, _: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| } | |
| ): (self: AsyncResult<A, E>) => W | X | Y | Z | |
| /** | |
| * Pattern matches a result by calling `onWaiting` for waiting or initial states, otherwise handling successes and splitting failures into typed errors or squashed non-error causes. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A, E, W, X, Y, Z>( | |
| self: AsyncResult<A, E>, | |
| options: { | |
| readonly onWaiting: (_: AsyncResult<A, E>) => W | |
| readonly onError: (error: E, _: Failure<A, E>) => X | |
| readonly onDefect: (defect: unknown, _: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| } | |
| ): W | X | Y | Z | |
| } = dual(2, <A, E, W, X, Y, Z>(self: AsyncResult<A, E>, options: { | |
| readonly onWaiting: (_: AsyncResult<A, E>) => W | |
| readonly onError: (error: E, _: Failure<A, E>) => X | |
| readonly onDefect: (defect: unknown, _: Failure<A, E>) => Y | |
| readonly onSuccess: (_: Success<A, E>) => Z | |
| }): W | X | Y | Z => { | |
| if (self.waiting) { | |
| return options.onWaiting(self) | |
| } | |
| switch (self._tag) { | |
| case "Initial": | |
| return options.onWaiting(self) | |
| case "Failure": { | |
| const e = Cause.findError(self.cause) | |
| if (Result.isFailure(e)) { | |
| return options.onDefect(Cause.squash(e.failure), self) | |
| } | |
| return options.onError(e.success, self) | |
| } | |
| case "Success": | |
| return options.onSuccess(self) | |
| } | |
| }) | |
| /** | |
| * Combines an iterable or record of `AsyncResult` and plain values into one `AsyncResult`, returning the first non-success result or a success of the collected values marked waiting when any input success is waiting. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const all = <const Arg extends Iterable<any> | Record<string, any>>( | |
| results: Arg | |
| ): AsyncResult< | |
| [Arg] extends [ReadonlyArray<any>] ? { | |
| -readonly [K in keyof Arg]: [Arg[K]] extends [AsyncResult<infer _A, infer _E>] ? _A : Arg[K] | |
| } | |
| : [Arg] extends [Iterable<infer _A>] ? _A extends AsyncResult<infer _AA, infer _E> ? _AA : _A | |
| : [Arg] extends [Record<string, any>] ? { | |
| -readonly [K in keyof Arg]: [Arg[K]] extends [AsyncResult<infer _A, infer _E>] ? _A : Arg[K] | |
| } | |
| : never, | |
| [Arg] extends [ReadonlyArray<any>] ? AsyncResult.Failure<Arg[number]> | |
| : [Arg] extends [Iterable<infer _A>] ? AsyncResult.Failure<_A> | |
| : [Arg] extends [Record<string, any>] ? AsyncResult.Failure<Arg[keyof Arg]> | |
| : never | |
| > => { | |
| const isIter = isIterable(results) | |
| const entries = isIter | |
| ? Array.from(results, (result, i) => [i, result] as const) | |
| : Object.entries(results) | |
| const successes: any = isIter ? [] : {} | |
| let waiting = false | |
| for (let i = 0; i < entries.length; i++) { | |
| const [key, result] = entries[i] | |
| if (!isAsyncResult(result)) { | |
| successes[key] = result | |
| continue | |
| } else if (!isSuccess(result)) { | |
| return result as any | |
| } | |
| successes[key] = result.value | |
| if (result.waiting) { | |
| waiting = true | |
| } | |
| } | |
| return success(successes, { waiting }) as any | |
| } | |
| /** | |
| * Creates a typed builder for rendering an `AsyncResult` by handling waiting, initial, success, error, defect, interrupt, and failure cases. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const builder = <A extends AsyncResult<any, any>>(self: A): Builder< | |
| never, | |
| A extends Success<infer _A, infer _E> ? _A : never, | |
| A extends Failure<infer _A, infer _E> ? _E : never, | |
| A extends Initial<infer _A, infer _E> ? true : never, | |
| A extends Failure<infer _A, infer _E> ? Defect | Interrupt : never | |
| > => new BuilderImpl(self) as any | |
| /** | |
| * Type marker used by `Builder` to track whether defect failures still need to be handled. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface Defect { | |
| readonly _: unique symbol | |
| } | |
| /** | |
| * Type marker used by `Builder` to track whether interrupt failures still need to be handled. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface Interrupt { | |
| readonly _: unique symbol | |
| } | |
| /** | |
| * Fluent renderer for `AsyncResult` values that tracks unhandled cases at the type level and exposes `exhaustive` only after all possible cases are handled. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export type Builder<Out, A, E, I, F> = | |
| & Pipeable | |
| & { | |
| onWaiting<B>(f: (result: AsyncResult<A, E>) => B): Builder<Out | B, A, E, I, F> | |
| orElse<B>(orElse: LazyArg<B>): Out | B | |
| orNull(): Out | null | |
| render(): [A | I] extends [never] ? Out : Out | null | |
| } | |
| & ([A | E | I | F] extends [never] ? { | |
| exhaustive(): Out | |
| } : | |
| unknown) | |
| & ([I] extends [never] ? unknown : | |
| { | |
| onInitial<B>(f: (result: Initial<A, E>) => B): Builder<Out | B, A, E, never, F> | |
| onInitialOrWaiting<B>(f: (result: AsyncResult<A, E>) => B): Builder<Out | B, A, E, never, F> | |
| }) | |
| & ([A] extends [never] ? unknown : | |
| { | |
| onSuccess<B>(f: (value: A, result: Success<A, E>) => B): Builder<Out | B, never, E, I, F> | |
| }) | |
| & ([E] extends [never] ? unknown : { | |
| onError<B>(f: (error: E, result: Failure<A, E>) => B): Builder<Out | B, A, never, I, F> | |
| onErrorIf<B extends E, C>( | |
| refinement: Refinement<E, B>, | |
| f: (error: B, result: Failure<A, E>) => C | |
| ): Builder<Out | C, A, Types.EqualsWith<E, B, E, Exclude<E, B>>, I, F> | |
| onErrorIf<C>( | |
| predicate: Predicate<E>, | |
| f: (error: E, result: Failure<A, E>) => C | |
| ): Builder<Out | C, A, E, I, F> | |
| onErrorTag<const Tags extends ReadonlyArray<Types.Tags<E>>, B>( | |
| tags: Tags, | |
| f: (error: Types.ExtractTag<E, Tags[number]>, result: Failure<A, E>) => B | |
| ): Builder<Out | B, A, Types.ExcludeTag<E, Tags[number]>, I, F> | |
| onErrorTag<const Tag extends Types.Tags<E>, B>( | |
| tag: Tag, | |
| f: (error: Types.ExtractTag<E, Tag>, result: Failure<A, E>) => B | |
| ): Builder<Out | B, A, Types.ExcludeTag<E, Tag>, I, F> | |
| }) | |
| & ([E | F] extends [never] ? unknown : { | |
| onFailure<B>(f: (cause: Cause.Cause<E>, result: Failure<A, E>) => B): Builder<Out | B, A, never, I, never> | |
| }) | |
| & (Interrupt extends F ? { | |
| onInterrupt<B>( | |
| f: (interruptors: ReadonlySet<number>, result: Failure<A, E>) => B | |
| ): Builder<Out | B, A, E, I, Exclude<F, Interrupt>> | |
| } : | |
| unknown) | |
| & (Defect extends F ? { | |
| onDefect<B>(f: (defect: unknown, result: Failure<A, E>) => B): Builder<Out | B, A, E, I, Exclude<F, Defect>> | |
| } : | |
| unknown) | |
| class BuilderImpl<Out, A, E> { | |
| constructor(result: AsyncResult<A, E>) { | |
| this.result = result | |
| } | |
| readonly result: AsyncResult<A, E> | |
| public output = Option.none<Out>() | |
| when<B extends AsyncResult<A, E>, C>( | |
| refinement: Refinement<AsyncResult<A, E>, B>, | |
| f: (result: B) => Option.Option<C> | |
| ): any | |
| when<C>( | |
| refinement: Predicate<AsyncResult<A, E>>, | |
| f: (result: AsyncResult<A, E>) => Option.Option<C> | |
| ): any | |
| when<C>( | |
| refinement: Predicate<AsyncResult<A, E>>, | |
| f: (result: AsyncResult<A, E>) => Option.Option<C> | |
| ): any { | |
| if (Option.isNone(this.output) && refinement(this.result)) { | |
| const b = f(this.result) | |
| if (Option.isSome(b)) { | |
| ;(this as any).output = b | |
| } | |
| } | |
| return this | |
| } | |
| pipe() { | |
| return pipeArguments(this, arguments) | |
| } | |
| onWaiting<B>(f: (result: AsyncResult<A, E>) => B): BuilderImpl<Out | B, A, E> { | |
| return this.when((r) => r.waiting, (r) => Option.some(f(r))) | |
| } | |
| onInitialOrWaiting<B>(f: (result: AsyncResult<A, E>) => B): BuilderImpl<Out | B, A, E> { | |
| return this.when((r) => isInitial(r) || r.waiting, (r) => Option.some(f(r))) | |
| } | |
| onInitial<B>(f: (result: Initial<A, E>) => B): BuilderImpl<Out | B, A, E> { | |
| return this.when(isInitial, (r) => Option.some(f(r))) | |
| } | |
| onSuccess<B>(f: (value: A, result: Success<A, E>) => B): BuilderImpl<Out | B, never, E> { | |
| return this.when(isSuccess, (r) => Option.some(f(r.value, r))) | |
| } | |
| onFailure<B>(f: (cause: Cause.Cause<E>, result: Failure<A, E>) => B): BuilderImpl<Out | B, A, never> { | |
| return this.when(isFailure, (r) => Option.some(f(r.cause, r))) | |
| } | |
| onError<B>(f: (error: E, result: Failure<A, E>) => B): BuilderImpl<Out | B, A, never> { | |
| return this.onErrorIf(constTrue, f) as any | |
| } | |
| onErrorIf<C, B extends E = E>( | |
| refinement: Refinement<E, B> | Predicate<E>, | |
| f: (error: B, result: Failure<A, E>) => C | |
| ): BuilderImpl<Out | C, A, Types.EqualsWith<E, B, E, Exclude<E, B>>> { | |
| return this.when(isFailure, (result) => | |
| Cause.findErrorOption(result.cause).pipe( | |
| Option.filter(refinement), | |
| Option.map((error) => f(error as B, result)) | |
| )) | |
| } | |
| onErrorTag<B>( | |
| tag: string | ReadonlyArray<string>, | |
| f: (error: Types.ExtractTag<E, any>, result: Failure<A, E>) => B | |
| ): BuilderImpl<Out | B, A, Types.ExcludeTag<E, any>> { | |
| return this.onErrorIf( | |
| (e) => hasProperty(e, "_tag") && (Array.isArray(tag) ? tag.includes(e._tag) : e._tag === tag), | |
| f | |
| ) as any | |
| } | |
| onDefect<B>(f: (defect: unknown, result: Failure<A, E>) => B): BuilderImpl<Out | B, A, E> { | |
| return this.when(isFailure, (result) => { | |
| const defect = Cause.findDefect(result.cause) | |
| return Result.isFailure(defect) ? Option.none() : Option.some(f(defect.success, result)) | |
| }) | |
| } | |
| onInterrupt<B>(f: (interruptors: ReadonlySet<number>, result: Failure<A, E>) => B): BuilderImpl<Out | B, A, E> { | |
| return this.when(isFailure, (result) => { | |
| const interruptors = Cause.filterInterruptors(result.cause) | |
| return Result.isFailure(interruptors) ? Option.none() : Option.some(f(interruptors.success, result)) | |
| }) | |
| } | |
| orElse<B>(orElse: LazyArg<B>): Out | B { | |
| return Option.getOrElse(this.output, orElse) | |
| } | |
| orNull(): Out | null { | |
| return Option.getOrNull(this.output) | |
| } | |
| render(): Out | null { | |
| if (Option.isSome(this.output)) { | |
| return this.output.value | |
| } else if (isFailure(this.result)) { | |
| throw Cause.squash(this.result.cause) | |
| } | |
| return null | |
| } | |
| exhaustive(): Out { | |
| return this.render() as Out | |
| } | |
| } | |
| /** | |
| * Schema interface for `AsyncResult` values, retaining the schemas used for success values and failure errors. | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| export interface Schema< | |
| Success extends Schema_.Top, | |
| Error extends Schema_.Top | |
| > extends | |
| Schema_.declareConstructor< | |
| AsyncResult<Success["Type"], Error["Type"]>, | |
| AsyncResult<Success["Encoded"], Error["Encoded"]>, | |
| readonly [Success, Schema_.Cause<Error, Schema_.Defect>] | |
| > | |
| { | |
| readonly success: Success | |
| readonly error: Error | |
| } | |
| /** | |
| * Creates a schema for `AsyncResult` values using optional schemas for success values and failure errors. | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| export const Schema = < | |
| A extends Schema_.Top = Schema_.Never, | |
| E extends Schema_.Top = Schema_.Never | |
| >( | |
| options: { | |
| readonly success?: A | undefined | |
| readonly error?: E | undefined | |
| } | |
| ): Schema<A, E> => { | |
| const success_: A = options.success ?? Schema_.Never as any | |
| const error: E = options.error ?? Schema_.Never as any | |
| const schema = Schema_.declareConstructor< | |
| AsyncResult<A["Type"], E["Type"]>, | |
| AsyncResult<A["Encoded"], E["Encoded"]> | |
| >()( | |
| [success_, Schema_.Cause(error, Schema_.Defect())], | |
| ([value, cause]) => (input, ast, options) => { | |
| if (!isAsyncResult(input)) { | |
| return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))) | |
| } | |
| switch (input._tag) { | |
| case "Initial": | |
| return Effect.succeed(input) | |
| case "Success": | |
| return Effect.mapBothEager( | |
| SchemaParser.decodeUnknownEffect(value)(input.value, options), | |
| { | |
| onSuccess: (value) => success(value, input), | |
| onFailure: (issue) => | |
| new SchemaIssue.Composite(ast, Option.some(input), [new SchemaIssue.Pointer(["value"], issue)]) | |
| } | |
| ) | |
| case "Failure": { | |
| const prevSuccessEffect = input.previousSuccess.pipe( | |
| Option.map((ps) => | |
| Effect.mapBothEager( | |
| SchemaParser.decodeUnknownEffect(value)(ps.value, options), | |
| { | |
| onSuccess: (value) => Option.some(success<A["Type"], E["Type"]>(value, ps)), | |
| onFailure: (issue) => | |
| new SchemaIssue.Composite(ast, Option.some(input), [ | |
| new SchemaIssue.Pointer(["previousSuccess", "value"], issue) | |
| ]) | |
| } | |
| ) | |
| ), | |
| Option.getOrElse(() => Effect.succeedNone) | |
| ) | |
| const causeEffect = Effect.mapErrorEager( | |
| SchemaParser.decodeUnknownEffect(cause)(input.cause, options), | |
| (issue) => new SchemaIssue.Composite(ast, Option.some(input), [new SchemaIssue.Pointer(["cause"], issue)]) | |
| ) | |
| return Effect.flatMapEager( | |
| prevSuccessEffect, | |
| (previousSuccess) => | |
| Effect.mapEager(causeEffect, (cause) => | |
| failure(cause, { | |
| previousSuccess, | |
| waiting: input.waiting | |
| })) | |
| ) | |
| } | |
| } | |
| }, | |
| { | |
| expected: "AsyncResult", | |
| toCodec([value, cause]) { | |
| const Success = Schema_.TaggedStruct("Success", { value, waiting: Schema_.Boolean, timestamp: Schema_.Number }) | |
| return Schema_.link<AsyncResult<A["Encoded"], E["Encoded"]>>()( | |
| Schema_.Union([ | |
| Schema_.TaggedStruct("Initial", { waiting: Schema_.Boolean }), | |
| Success, | |
| Schema_.TaggedStruct("Failure", { | |
| cause, | |
| previousSuccess: Schema_.Option(Success), | |
| waiting: Schema_.Boolean | |
| }) | |
| ]), | |
| SchemaTransformation.transform({ | |
| decode: (encoded): AsyncResult<A["Encoded"], E["Encoded"]> => { | |
| switch (encoded._tag) { | |
| case "Initial": | |
| return initial(encoded.waiting) | |
| case "Success": | |
| return success(encoded.value, { | |
| waiting: encoded.waiting, | |
| timestamp: encoded.timestamp | |
| }) | |
| case "Failure": { | |
| return failure(encoded.cause, { | |
| previousSuccess: Option.map(encoded.previousSuccess, (ps) => success(ps.value, ps)), | |
| waiting: encoded.waiting | |
| }) | |
| } | |
| } | |
| }, | |
| encode(result) { | |
| switch (result._tag) { | |
| case "Initial": | |
| return { _tag: "Initial" as const, waiting: result.waiting } | |
| case "Success": | |
| return { | |
| _tag: "Success" as const, | |
| value: result.value, | |
| waiting: result.waiting, | |
| timestamp: result.timestamp | |
| } | |
| case "Failure": | |
| return { | |
| _tag: "Failure" as const, | |
| cause: result.cause, | |
| previousSuccess: result.previousSuccess, | |
| waiting: result.waiting | |
| } | |
| } | |
| } | |
| }) | |
| ) | |
| }, | |
| toEquivalence: Equal.asEquivalence, | |
| toFormatter: ([value, cause]) => (t) => { | |
| switch (t._tag) { | |
| case "Success": | |
| return `AsyncResult.Success(${value(t.value)}, ${t.waiting}, ${t.timestamp})` | |
| case "Failure": | |
| return `AsyncResult.Failure(${cause(t.cause)}, ${t.waiting})` | |
| case "Initial": | |
| return `AsyncResult.Initial(${t.waiting}, ${t.waiting})` | |
| } | |
| } | |
| } | |
| ) | |
| return Object.assign(schema, { | |
| success: success_, | |
| error | |
| }) | |
| } | |
Xet Storage Details
- Size:
- 39.4 kB
- Xet hash:
- 2a425e61384457a2df84368caa237213eefc4eca05567cfe3c2f4d8acfc9cb33
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.