From c380fe9d08069520aa81139b340cd053512718ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 28 Oct 2025 21:03:31 +0100 Subject: [PATCH] Result work --- packages/effect-fc/src/Result.ts | 77 +++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/packages/effect-fc/src/Result.ts b/packages/effect-fc/src/Result.ts index f065082..9b64f90 100644 --- a/packages/effect-fc/src/Result.ts +++ b/packages/effect-fc/src/Result.ts @@ -1,4 +1,4 @@ -import { Cause, Effect, Equal, Exit, Hash, Match, Option, Pipeable, Predicate, pipe, Queue, type Scope } from "effect" +import { Cause, Context, Data, Effect, Equal, Exit, Hash, Layer, Match, Option, Pipeable, Predicate, pipe, Queue, Ref, type Scope } from "effect" export const ResultTypeId: unique symbol = Symbol.for("@effect-fc/Result/Result") @@ -95,14 +95,44 @@ const ResultPrototype = Object.freeze({ } as const satisfies Result.Prototype) -export interface ProgressService

{ - readonly update: (progress: P) => Effect.Effect +export interface Progress

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

(): Context.Tag, Progress

> => Context.GenericTag("@effect-fc/Result/Progress") + +export const makeProgressLayer = ( + queue: Queue.Enqueue>, + ref: Ref.Ref>, +): Layer.Layer> => Layer.sync(Progress

(), () => ({ + update: (f: (previous: P) => Effect.Effect) => Effect.Do.pipe( + Effect.bind("previous", () => Effect.andThen( + ref, + previous => isRunning(previous) || isRefreshing(previous) + ? Effect.succeed(previous) + : Effect.fail(new PreviousResultNotRunningOrRefreshing({ previous })), + )), + Effect.bind("progress", ({ previous }) => f(previous.progress)), + Effect.let("next", ({ previous, progress }) => Object.setPrototypeOf( + Object.assign({}, previous, { progress }), + Object.getPrototypeOf(previous), + )), + Effect.tap(({ next }) => Ref.set(ref, next)), + Effect.tap(({ next }) => Queue.offer(queue, next)), + Effect.asVoid, + ) +})) + 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 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 @@ -146,15 +176,32 @@ export const toExit = ( } } -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, - ))), +export const forkEffectScoped = ( + effect: Effect.Effect> | R>, + initialProgress?: P, +): Effect.Effect< + Queue.Dequeue>, + never, + Scope.Scope | R +> => Effect.Do.pipe( + Effect.bind("queue", () => Queue.unbounded>()), + Effect.bind("ref", () => Ref.make>(initial())), + Effect.tap(({ queue, ref }) => Effect.andThen(ref, v => Queue.offer(queue, v))), + Effect.tap(({ queue, ref }) => Effect.forkScoped( + Effect.addFinalizer(() => Queue.shutdown(queue)).pipe( + Effect.andThen(Effect.succeed(running(initialProgress)).pipe( + Effect.tap(v => Ref.set(ref, v)), + Effect.tap(v => Queue.offer(queue, v)), + )), + Effect.andThen(effect), + Effect.exit, + Effect.andThen(exit => Effect.succeed(fromExit(exit)).pipe( + Effect.tap(v => Ref.set(ref, v)), + Effect.tap(v => Queue.offer(queue, v)), + )), + Effect.scoped, + Effect.provide(makeProgressLayer(queue, ref)), + ) + )), + Effect.map(({ queue }) => queue), )