import { Cause, Context, Data, Effect, Equal, Exit, type Fiber, Hash, Layer, Match, pipe, Pipeable, Predicate, PubSub, Ref, type Scope, Stream, type Subscribable, SynchronizedRef } from "effect" import { Lens } from "effect-lens" export const ResultTypeId: unique symbol = Symbol.for("@effect-fc/Result/Result") export type ResultTypeId = typeof ResultTypeId export type Result = ( | Initial | Running

| Final ) // biome-ignore lint/complexity/noBannedTypes: "{}" is relevant here export type Final = (Success | Failure) & ({} | Flags

) export type Flags

= WillFetch | WillRefresh | Refreshing

export declare namespace Result { 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 declare namespace Flags { export type Keys = keyof WillFetch & WillRefresh & Refreshing } export interface Initial extends ResultPrototype { readonly _tag: "Initial" } export interface Running

extends ResultPrototype { readonly _tag: "Running" readonly progress: P } export interface Success extends ResultPrototype { readonly _tag: "Success" readonly value: A } export interface Failure extends ResultPrototype { readonly _tag: "Failure" readonly cause: Cause.Cause } export interface WillFetch { readonly _flag: "WillFetch" } export interface WillRefresh { readonly _flag: "WillRefresh" } export interface Refreshing

{ readonly _flag: "Refreshing" readonly progress: P } export interface ResultPrototype extends Pipeable.Pipeable, Equal.Equal { readonly [ResultTypeId]: ResultTypeId } export const ResultPrototype: ResultPrototype = Object.freeze({ ...Pipeable.Prototype, [ResultTypeId]: ResultTypeId, [Equal.symbol](this: Result, that: Result): boolean { if (this._tag !== that._tag || (this as Flags)._flag !== (that as Flags)._flag) return false if (hasRefreshingFlag(this) && !Equal.equals(this.progress, (that as Refreshing).progress)) 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)), Match.tag("Failure", self => Equal.equals(self.cause, (that as Failure).cause)), Match.exhaustive, ) }, [Hash.symbol](this: Result): number { return pipe(Hash.string(this._tag), tagHash => Match.value(this).pipe( Match.tag("Initial", () => tagHash), Match.tag("Running", self => Hash.combine(Hash.hash(self.progress))(tagHash)), Match.tag("Success", self => Hash.combine(Hash.hash(self.value))(tagHash)), Match.tag("Failure", self => Hash.combine(Hash.hash(self.cause))(tagHash)), Match.exhaustive, ), Hash.combine(Hash.hash((this as Flags)._flag)), hash => hasRefreshingFlag(this) ? Hash.combine(Hash.hash(this.progress))(hash) : hash, Hash.cached(this), ) }, } as const) export const isResult = (u: unknown): u is Result => Predicate.hasProperty(u, ResultTypeId) export const isFinal = (u: unknown): u is Final => isResult(u) && (isSuccess(u) || isFailure(u)) 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 hasFlag = (u: unknown): u is Flags => isResult(u) && Predicate.hasProperty(u, "_flag") export const hasWillFetchFlag = (u: unknown): u is WillFetch => isResult(u) && Predicate.hasProperty(u, "_flag") && u._flag === "WillFetch" export const hasWillRefreshFlag = (u: unknown): u is WillRefresh => isResult(u) && Predicate.hasProperty(u, "_flag") && u._flag === "WillRefresh" export const hasRefreshingFlag = (u: unknown): u is Refreshing => isResult(u) && Predicate.hasProperty(u, "_flag") && u._flag === "Refreshing" export const initial: { (): Initial (): Result } = (): 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 ): Failure => Object.setPrototypeOf({ _tag: "Failure", cause }, ResultPrototype) export const willFetch = >( result: R ): Omit & WillFetch => Object.setPrototypeOf( Object.assign({}, result, { _flag: "WillFetch" }), Object.getPrototypeOf(result), ) export const willRefresh = >( result: R ): Omit & WillRefresh => Object.setPrototypeOf( Object.assign({}, result, { _flag: "WillRefresh" }), Object.getPrototypeOf(result), ) export const refreshing = , P = never>( result: R, progress?: P, ): Omit & Refreshing

=> Object.setPrototypeOf( Object.assign({}, result, { _flag: "Refreshing", progress }), Object.getPrototypeOf(result), ) export const fromExit: { (exit: Exit.Success): Success (exit: Exit.Failure): Failure (exit: Exit.Exit): Success | Failure } = exit => (exit._tag === "Success" ? succeed(exit.value) : fail(exit.cause)) as any export const toExit: { (self: Success): Exit.Success (self: Failure): Exit.Failure (self: Final): Exit.Exit (self: Result): Exit.Exit } = (self: Result): any => { 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 Progress

{ readonly progress: Lens.Lens } export const Progress =

(): Context.Tag, Progress

> => Context.GenericTag("@effect-fc/Result/Progress") export class PreviousResultNotRunningNorRefreshing extends Data.TaggedError("@effect-fc/Result/PreviousResultNotRunningNorRefreshing")<{ readonly previous: Result }> {} export const makeProgressLayer = ( state: Lens.Lens, never, never, never, never> ): Layer.Layer | Progress, never, never> => Layer.succeed( Progress

() as Context.Tag | Progress, Progress

| Progress>, { progress: state.pipe( Lens.mapEffect( a => (isRunning(a) || hasRefreshingFlag(a)) ? Effect.succeed(a) : Effect.fail(new PreviousResultNotRunningNorRefreshing({ previous: a })), (_, b) => Effect.succeed(b), ), Lens.map( a => a.progress, (a, b) => isRunning(a) ? running(b) : refreshing(a, b) as Final & Refreshing

, ), ) }, ) export namespace unsafeForkEffect { export type OutputContext = Exclude | Progress> export interface Options { readonly initial?: Initial | Final readonly initialProgress?: P } } export const unsafeForkEffect = Effect.fnUntraced(function* ( effect: Effect.Effect, options?: unsafeForkEffect.Options, NoInfer, P>, ): Effect.fn.Return< readonly [result: Subscribable.Subscribable, never, never>, fiber: Fiber.Fiber], never, Scope.Scope | unsafeForkEffect.OutputContext > { const ref = yield* SynchronizedRef.make>(options?.initial ?? initial()) const pubsub = yield* PubSub.unbounded>() const state = Lens.make, never, never, never, never>({ get get() { return Ref.get(ref) }, get changes() { return Stream.unwrapScoped(Effect.map( Effect.all([Ref.get(ref), Stream.fromPubSub(pubsub, { scoped: true })]), ([latest, stream]) => Stream.concat(Stream.make(latest), stream), )) }, modify: f => Ref.get(ref).pipe( Effect.flatMap(f), Effect.flatMap(([b, a]) => Ref.set(ref, a).pipe( Effect.as(b), Effect.zipLeft(PubSub.publish(pubsub, a)) )), ), }) const fiber = yield* Effect.gen(function*() { yield* Lens.set( state, (isFinal(options?.initial) && hasWillRefreshFlag(options?.initial)) ? refreshing(options.initial, options?.initialProgress) as Result : running(options?.initialProgress), ) return yield* Effect.onExit(effect, exit => Effect.andThen( Lens.set(state, fromExit(exit)), Effect.forkScoped(PubSub.shutdown(pubsub)), )) }).pipe( Effect.forkScoped, Effect.provide(makeProgressLayer(state)), ) return [state, fiber] as const }) export namespace forkEffect { export type InputContext = R extends Progress ? [X] extends [P] ? R : never : R export type OutputContext = unsafeForkEffect.OutputContext export interface Options extends unsafeForkEffect.Options {} } export const forkEffect: { ( effect: Effect.Effect>>, options?: forkEffect.Options, NoInfer, P>, ): Effect.Effect< readonly [result: Subscribable.Subscribable, never, never>, fiber: Fiber.Fiber], never, Scope.Scope | forkEffect.OutputContext > } = unsafeForkEffect