import { Cause, Context, Data, Effect, Equal, Exit, Hash, Layer, Match, Option, Pipeable, Predicate, PubSub, pipe, type Queue, Ref, type Scope, type Subscribable, SubscriptionRef } 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 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 interface State { readonly get: Effect.Effect> readonly set: (v: Result) => Effect.Effect } export const State = (): Context.Tag, State> => Context.GenericTag("@effect-fc/Result/State") export interface Progress

{ readonly update: ( f: (previous: P) => Effect.Effect ) => Effect.Effect } export class PreviousResultNotRunningNorRefreshing extends Data.TaggedError("@effect-fc/Result/PreviousResultNotRunningNorRefreshing")<{ readonly previous: Result }> {} export const Progress =

(): Context.Tag, Progress

> => Context.GenericTag("@effect-fc/Result/Progress") export const makeProgressLayer = (): Layer.Layer< Progress

, never, State > => Layer.effect(Progress

(), Effect.gen(function*() { const state = yield* State() return { update: (f: (previous: P) => Effect.Effect) => Effect.Do.pipe( Effect.bind("previous", () => Effect.andThen(state.get, previous => isRunning(previous) || isRefreshing(previous) ? Effect.succeed(previous) : Effect.fail(new PreviousResultNotRunningNorRefreshing({ previous })), )), Effect.bind("progress", ({ previous }) => f(previous.progress)), Effect.let("next", ({ previous, progress }) => Object.setPrototypeOf( Object.assign({}, previous, { progress }), Object.getPrototypeOf(previous), )), Effect.andThen(({ next }) => state.set(next)), ), } })) export namespace forkEffectSubscriptionRef { export type InputContext = R extends Progress ? [X] extends [P] ? R : never : R export type OutputContext = Scope.Scope | Exclude | Progress> export interface Options

{ readonly initialProgress?: P } } export const forkEffectSubscriptionRef = ( effect: Effect.Effect>>, options?: forkEffectSubscriptionRef.Options

, ): Effect.Effect< Subscribable.Subscribable>, never, forkEffectSubscriptionRef.OutputContext > => Effect.tap( SubscriptionRef.make>(initial()), ref => Effect.forkScoped(State().pipe( Effect.andThen(state => state.set(running(options?.initialProgress)).pipe( Effect.andThen(effect), Effect.onExit(exit => state.set(fromExit(exit))), )), Effect.provide(Layer.empty.pipe( Layer.provideMerge(makeProgressLayer()), Layer.provideMerge(Layer.succeed(State(), { get: ref, set: v => Ref.set(ref, v), })), )), )), ) as Effect.Effect>, never, Scope.Scope> export namespace forkEffectPubSub { export type InputContext = R extends Progress ? [X] extends [P] ? R : never : R export type OutputContext = Scope.Scope | Exclude | Progress> export interface Options

{ readonly initialProgress?: P } } export const forkEffectPubSub = ( effect: Effect.Effect>>, options?: forkEffectPubSub.Options

, ): Effect.Effect< Effect.Effect>, never, Scope.Scope>, never, forkEffectPubSub.OutputContext > => Effect.all([ Ref.make>(initial()), PubSub.unbounded>(), ]).pipe( Effect.tap(([ref, pubsub]) => Effect.forkScoped(State().pipe( Effect.andThen(state => state.set(running(options?.initialProgress)).pipe( Effect.andThen(effect), Effect.onExit(exit => Effect.andThen( state.set(fromExit(exit)), Effect.forkScoped(PubSub.shutdown(pubsub)), )), )), Effect.provide(Layer.empty.pipe( Layer.provideMerge(makeProgressLayer()), Layer.provideMerge(Layer.succeed(State(), { get: ref, set: v => Effect.andThen(Ref.set(ref, v), PubSub.publish(pubsub, v)) })), )), ))), Effect.map(([, pubsub]) => pubsub.subscribe), ) as Effect.Effect>, never, Scope.Scope>, never, Scope.Scope> export namespace forkEffect { export type InputContext = R extends Progress ? [X] extends [P] ? R : never : R export type OutputContext = Scope.Scope | Exclude | Progress> export interface Options

{ readonly initialProgress?: P } } export const forkEffect = ( effect: Effect.Effect>>, options?: forkEffect.Options

, ) => Effect.all([ Ref.make>(initial()), PubSub.unbounded>(), ]).pipe( Effect.tap(([ref, pubsub]) => Effect.forkScoped(State().pipe( Effect.andThen(state => state.set(running(options?.initialProgress)).pipe( Effect.andThen(effect), Effect.onExit(exit => Effect.andThen( state.set(fromExit(exit)), Effect.forkScoped(PubSub.shutdown(pubsub)), )), )), Effect.provide(Layer.empty.pipe( Layer.provideMerge(makeProgressLayer()), Layer.provideMerge(Layer.succeed(State(), { get: ref, set: v => Effect.andThen(Ref.set(ref, v), PubSub.publish(pubsub, v)) })), )), ))), Effect.map(([, pubsub]) => pubsub.subscribe), ) as Effect.Effect>, never, Scope.Scope>, never, Scope.Scope>