import { Cause, Effect, Equal, Exit, Hash, Match, Option, Pipeable, Predicate, pipe, Queue, type Scope } from "effect" export const ResultTypeId: unique symbol = Symbol.for("@effect-fc/Result/Result") export type ResultTypeId = typeof ResultTypeId export type Result = ( | Initial | Running

| Success | (Success & Refreshing

) | Failure | (Failure & Refreshing

) ) export namespace Result { export interface Prototype extends Pipeable.Pipeable, Equal.Equal { readonly [ResultTypeId]: ResultTypeId } export type Success> = [R] extends [Result] ? A : never export type Failure> = [R] extends [Result] ? E : never export type Progress> = [R] extends [Result] ? P : never } export interface Initial extends Result.Prototype { readonly _tag: "Initial" } export interface Running

extends Result.Prototype { readonly _tag: "Running" readonly progress: P } export interface Success extends Result.Prototype { readonly _tag: "Success" readonly value: A } export interface Failure extends Result.Prototype { readonly _tag: "Failure" readonly cause: Cause.Cause readonly previousSuccess: Option.Option> } export interface Refreshing

{ readonly refreshing: true readonly progress: P } const ResultPrototype = Object.freeze({ ...Pipeable.Prototype, [ResultTypeId]: ResultTypeId, [Equal.symbol](this: Result, that: Result): boolean { if (this._tag !== that._tag) return false return Match.value(this).pipe( Match.tag("Initial", () => true), Match.tag("Running", self => Equal.equals(self.progress, (that as Running).progress)), Match.tag("Success", self => Equal.equals(self.value, (that as Success).value) && (isRefreshing(self) ? self.refreshing : false) === (isRefreshing(that) ? that.refreshing : false) && Equal.equals(isRefreshing(self) ? self.progress : undefined, isRefreshing(that) ? that.progress : undefined) ), Match.tag("Failure", self => Equal.equals(self.cause, (that as Failure).cause) && (isRefreshing(self) ? self.refreshing : false) === (isRefreshing(that) ? that.refreshing : false) && Equal.equals(isRefreshing(self) ? self.progress : undefined, isRefreshing(that) ? that.progress : undefined) ), Match.exhaustive, ) }, [Hash.symbol](this: Result): number { const tagHash = Hash.string(this._tag) return Match.value(this).pipe( Match.tag("Initial", () => tagHash), Match.tag("Running", self => Hash.combine(Hash.hash(self.progress))(tagHash)), Match.tag("Success", self => pipe(tagHash, Hash.combine(Hash.hash(self.value)), Hash.combine(Hash.hash(isRefreshing(self) ? self.progress : undefined)), )), Match.tag("Failure", self => pipe(tagHash, Hash.combine(Hash.hash(self.cause)), Hash.combine(Hash.hash(isRefreshing(self) ? self.progress : undefined)), )), Match.exhaustive, Hash.cached(this), ) }, } as const satisfies Result.Prototype) export interface ProgressService

{ readonly update: (progress: P) => Effect.Effect } export const isResult = (u: unknown): u is Result => Predicate.hasProperty(u, ResultTypeId) export const isInitial = (u: unknown): u is Initial => isResult(u) && u._tag === "Initial" export const isRunning = (u: unknown): u is Running => isResult(u) && u._tag === "Running" export const isSuccess = (u: unknown): u is Success => isResult(u) && u._tag === "Success" export const isFailure = (u: unknown): u is Failure => isResult(u) && u._tag === "Failure" export const isRefreshing = (u: unknown): u is Refreshing => isResult(u) && Predicate.hasProperty(u, "refreshing") && u.refreshing export const initial = (): Initial => Object.setPrototypeOf({ _tag: "Initial" }, ResultPrototype) export const running =

(progress?: P): Running

=> Object.setPrototypeOf({ _tag: "Running", progress }, ResultPrototype) export const succeed = (value: A): Success => Object.setPrototypeOf({ _tag: "Success", value }, ResultPrototype) export const fail = ( cause: Cause.Cause, previousSuccess?: Success, ): Failure => Object.setPrototypeOf({ _tag: "Failure", cause, previousSuccess: Option.fromNullable(previousSuccess), }, ResultPrototype) export const refreshing = | Failure, P = never>( result: R, progress?: P, ): Omit>> & Refreshing

=> Object.setPrototypeOf( Object.assign({}, result, { progress }), Object.getPrototypeOf(result), ) export const fromExit = ( exit: Exit.Exit ): Success | Failure => exit._tag === "Success" ? succeed(exit.value) : fail(exit.cause) export const toExit = ( self: Result ): Exit.Exit => { switch (self._tag) { case "Success": return Exit.succeed(self.value) case "Failure": return Exit.failCause(self.cause) default: return Exit.fail(new Cause.NoSuchElementException()) } } export const forkEffectScoped = ( effect: Effect.Effect ): Effect.Effect>, never, Scope.Scope | R> => Queue.unbounded>().pipe( Effect.tap(Queue.offer(initial())), Effect.tap(queue => Effect.forkScoped(Effect.addFinalizer(() => Queue.shutdown(queue)).pipe( Effect.andThen(Queue.offer(queue, running())), Effect.andThen(effect), Effect.exit, Effect.andThen(exit => Queue.offer(queue, fromExit(exit))), Effect.scoped, ))), )