EdgeAIG's picture
download
raw
20.4 kB
/**
* 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.js";
import * as Effect from "../../Effect.js";
import * as Equal from "../../Equal.js";
import * as Exit from "../../Exit.js";
import { constTrue, dual, identity } from "../../Function.js";
import * as Hash from "../../Hash.js";
import * as Option from "../../Option.js";
import { pipeArguments } from "../../Pipeable.js";
import { hasProperty, isIterable } from "../../Predicate.js";
import * as Result from "../../Result.js";
import * as Schema_ from "../../Schema.js";
import * as SchemaIssue from "../../SchemaIssue.js";
import * as SchemaParser from "../../SchemaParser.js";
import * as SchemaTransformation from "../../SchemaTransformation.js";
/**
* Runtime identifier attached to `AsyncResult` values and used by `isAsyncResult`.
*
* @category type IDs
* @since 4.0.0
*/
export const TypeId = "~effect/reactivity/AsyncResult";
/**
* Returns `true` when a value is an `AsyncResult`.
*
* @category guards
* @since 4.0.0
*/
export const isAsyncResult = u => hasProperty(u, TypeId);
const ResultProto = {
[TypeId]: {
E: identity,
A: identity
},
pipe() {
return pipeArguments(this, arguments);
},
[Equal.symbol](that) {
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.value);
case "Failure":
return Equal.equals(this.cause, that.cause);
}
},
[Hash.symbol]() {
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 = result => result.waiting;
/**
* 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 = exit => 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 = (exit, previous) => 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 = previous => {
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 = result => result._tag === "Initial";
/**
* Returns `true` when an `AsyncResult` is either `Success` or `Failure`.
*
* @category refinements
* @since 4.0.0
*/
export const isNotInitial = result => result._tag !== "Initial";
/**
* Creates an `Initial` result, optionally marking it as waiting.
*
* @category constructors
* @since 4.0.0
*/
export const initial = (waiting = false) => {
const result = Object.create(ResultProto);
result._tag = "Initial";
result.waiting = waiting;
return result;
};
/**
* Returns `true` when an `AsyncResult` is a `Success`.
*
* @category refinements
* @since 4.0.0
*/
export const isSuccess = result => 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 = (value, options) => {
const result = Object.create(ResultProto);
result._tag = "Success";
result.value = value;
result.waiting = options?.waiting ?? false;
result.timestamp = options?.timestamp ?? Date.now();
return result;
};
/**
* Returns `true` when an `AsyncResult` is a `Failure`.
*
* @category refinements
* @since 4.0.0
*/
export const isFailure = result => 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 = result => 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 = (cause, options) => {
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 = (cause, options) => 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 = (error, options) => 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 = (error, options) => 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 = (self, options) => {
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.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 = result => {
if (isSuccess(result)) {
return success(result.value, {
waiting: result.waiting
});
}
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 = (self, previous) => {
if (self._tag === "Failure") {
return failureWithPrevious(self.cause, {
previous,
waiting: self.waiting
});
}
return self;
};
/**
* 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 = self => {
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 = /*#__PURE__*/dual(2, (self, orElse) => 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 = self => 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 = self => 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 = self => 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 = self => {
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 = /*#__PURE__*/dual(2, (self, f) => {
switch (self._tag) {
case "Initial":
return self;
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 = /*#__PURE__*/dual(2, (self, f) => {
switch (self._tag) {
case "Initial":
return self;
case "Failure":
return failure(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 = /*#__PURE__*/dual(2, (self, options) => {
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 = /*#__PURE__*/dual(2, (self, options) => {
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 = /*#__PURE__*/dual(2, (self, options) => {
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 = results => {
const isIter = isIterable(results);
const entries = isIter ? Array.from(results, (result, i) => [i, result]) : Object.entries(results);
const successes = 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;
}
successes[key] = result.value;
if (result.waiting) {
waiting = true;
}
}
return success(successes, {
waiting
});
};
/**
* 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 = self => new BuilderImpl(self);
class BuilderImpl {
constructor(result) {
this.result = result;
}
result;
output = /*#__PURE__*/Option.none();
when(refinement, f) {
if (Option.isNone(this.output) && refinement(this.result)) {
const b = f(this.result);
if (Option.isSome(b)) {
;
this.output = b;
}
}
return this;
}
pipe() {
return pipeArguments(this, arguments);
}
onWaiting(f) {
return this.when(r => r.waiting, r => Option.some(f(r)));
}
onInitialOrWaiting(f) {
return this.when(r => isInitial(r) || r.waiting, r => Option.some(f(r)));
}
onInitial(f) {
return this.when(isInitial, r => Option.some(f(r)));
}
onSuccess(f) {
return this.when(isSuccess, r => Option.some(f(r.value, r)));
}
onFailure(f) {
return this.when(isFailure, r => Option.some(f(r.cause, r)));
}
onError(f) {
return this.onErrorIf(constTrue, f);
}
onErrorIf(refinement, f) {
return this.when(isFailure, result => Cause.findErrorOption(result.cause).pipe(Option.filter(refinement), Option.map(error => f(error, result))));
}
onErrorTag(tag, f) {
return this.onErrorIf(e => hasProperty(e, "_tag") && (Array.isArray(tag) ? tag.includes(e._tag) : e._tag === tag), f);
}
onDefect(f) {
return this.when(isFailure, result => {
const defect = Cause.findDefect(result.cause);
return Result.isFailure(defect) ? Option.none() : Option.some(f(defect.success, result));
});
}
onInterrupt(f) {
return this.when(isFailure, result => {
const interruptors = Cause.filterInterruptors(result.cause);
return Result.isFailure(interruptors) ? Option.none() : Option.some(f(interruptors.success, result));
});
}
orElse(orElse) {
return Option.getOrElse(this.output, orElse);
}
orNull() {
return Option.getOrNull(this.output);
}
render() {
if (Option.isSome(this.output)) {
return this.output.value;
} else if (isFailure(this.result)) {
throw Cause.squash(this.result.cause);
}
return null;
}
exhaustive() {
return this.render();
}
}
/**
* Creates a schema for `AsyncResult` values using optional schemas for success values and failure errors.
*
* @category schemas
* @since 4.0.0
*/
export const Schema = options => {
const success_ = options.success ?? Schema_.Never;
const error = options.error ?? Schema_.Never;
const schema = Schema_.declareConstructor()([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(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()(Schema_.Union([Schema_.TaggedStruct("Initial", {
waiting: Schema_.Boolean
}), Success, Schema_.TaggedStruct("Failure", {
cause,
previousSuccess: Schema_.Option(Success),
waiting: Schema_.Boolean
})]), SchemaTransformation.transform({
decode: 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",
waiting: result.waiting
};
case "Success":
return {
_tag: "Success",
value: result.value,
waiting: result.waiting,
timestamp: result.timestamp
};
case "Failure":
return {
_tag: "Failure",
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
});
};
//# sourceMappingURL=AsyncResult.js.map

Xet Storage Details

Size:
20.4 kB
·
Xet hash:
de183195e30d16fbcbda790ff02bc0b8bee6fae48ecfe66e24af807c9941f1ba

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.