From 0f79f126326c5b7075dc681720f411088d85b689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 14 Jan 2026 09:18:34 +0100 Subject: [PATCH] Refactor Result --- packages/effect-fc/src/Result.ts | 86 ++++++++++++++++---------------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/packages/effect-fc/src/Result.ts b/packages/effect-fc/src/Result.ts index 349434a..fe7bdf3 100644 --- a/packages/effect-fc/src/Result.ts +++ b/packages/effect-fc/src/Result.ts @@ -11,13 +11,11 @@ export type Result = ( | Final ) -export type Final = ( - & (Success | Failure) - // biome-ignore lint/complexity/noBannedTypes: "{}" is relevant here - & ({} | WillFetch | WillRefresh | Refreshing

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

) +export type Flags

= WillFetch | WillRefresh | Refreshing

-export namespace Result { +export declare namespace Result { export interface Prototype extends Pipeable.Pipeable, Equal.Equal { readonly [ResultTypeId]: ResultTypeId } @@ -27,6 +25,10 @@ export namespace Result { export type Progress> = [R] extends [Result] ? P : never } +export declare namespace Flags { + export type Keys = keyof WillFetch & WillRefresh & Refreshing +} + export interface Initial extends Result.Prototype { readonly _tag: "Initial" } @@ -47,16 +49,16 @@ export interface Failure extends Result.Prototype { readonly previousSuccess: Option.Option> } -export interface WillFetch extends Result.Prototype { - readonly willFetch: true +export interface WillFetch { + readonly _flag: "WillFetch" } -export interface WillRefresh extends Result.Prototype { - readonly willRefresh: true +export interface WillRefresh { + readonly _flag: "WillRefresh" } -export interface Refreshing

extends Result.Prototype { - readonly refreshing: true +export interface Refreshing

{ + readonly _flag: "Refreshing" readonly progress: P } @@ -113,9 +115,9 @@ export const isInitial = (u: unknown): u is Initial => isResult(u) && u._tag === 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 isWillFetch = (u: unknown): u is WillFetch => isResult(u) && Predicate.hasProperty(u, "willFetch") -export const isWillRefresh = (u: unknown): u is WillRefresh => isResult(u) && Predicate.hasProperty(u, "willRefresh") -export const isRefreshing = (u: unknown): u is Refreshing => isResult(u) && Predicate.hasProperty(u, "refreshing") +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 @@ -135,23 +137,23 @@ export const fail = ( export const willFetch = >( result: R -): Omit & WillFetch => Object.setPrototypeOf( - Object.assign({}, result, { willFetch: true }), +): Omit & WillFetch => Object.setPrototypeOf( + Object.assign({}, result, { _flag: "WillFetch" }), Object.getPrototypeOf(result), ) export const willRefresh = >( result: R -): Omit & WillRefresh => Object.setPrototypeOf( - Object.assign({}, result, { willRefresh: true }), +): 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, { refreshing: true, progress }), +): Omit & Refreshing

=> Object.setPrototypeOf( + Object.assign({}, result, { _flag: "Refreshing", progress }), Object.getPrototypeOf(result), ) @@ -203,17 +205,17 @@ export const makeProgressLayer = (): Layer.Layer< const state = yield* State() return { - update: (f: (previous: P) => Effect.Effect) => Effect.Do.pipe( + update: (f: (previous: P) => Effect.Effect) => Effect.Do.pipe( Effect.bind("previous", () => Effect.andThen(state.get, previous => - isRunning(previous) || isRefreshing(previous) + (isRunning(previous) || hasRefreshingFlag(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.let("next", ({ previous, progress }) => isRunning(previous) + ? running(progress) + : refreshing(previous, progress) as Final & Refreshing

+ ), Effect.andThen(({ next }) => state.set(next)), ), } @@ -223,19 +225,11 @@ export const makeProgressLayer = (): Layer.Layer< export namespace unsafeForkEffect { export type OutputContext = Exclude | Progress

| Progress> - export type Options = { - readonly initial?: Result + export interface Options { + // biome-ignore lint/complexity/noBannedTypes: "{}" is relevant here + readonly initial?: (Initial | Success | Failure) & ({} | WillFetch | WillRefresh) readonly initialProgress?: P - readonly previous?: Final - } & ( - | { - readonly refresh: true - readonly previous: Final - } - | { - readonly refresh?: false - } - ) + } } export const unsafeForkEffect = ( @@ -249,13 +243,17 @@ export const unsafeForkEffect = ( Effect.bind("ref", () => Ref.make(options?.initial ?? initial())), Effect.bind("pubsub", () => PubSub.unbounded>()), Effect.bind("fiber", ({ ref, pubsub }) => Effect.forkScoped(State().pipe( - Effect.andThen(state => state.set(options?.refresh - ? refreshing(options.previous, options?.initialProgress) as Result - : running(options?.initialProgress) + Effect.andThen(state => state.set( + (isFinal(options?.initial) && hasWillRefreshFlag(options?.initial)) + ? refreshing(options.initial, options?.initialProgress) as Result + : running(options?.initialProgress) ).pipe( Effect.andThen(effect), Effect.onExit(exit => Effect.andThen( - state.set(fromExit(exit, (options?.previous && isSuccess(options.previous)) ? options.previous : undefined)), + state.set(fromExit( + exit, + isSuccess(options?.initial) ? options.initial : undefined), + ), Effect.forkScoped(PubSub.shutdown(pubsub)), )), )), @@ -286,7 +284,7 @@ export const unsafeForkEffect = ( export namespace forkEffect { export type InputContext = R extends Progress ? [X] extends [P] ? R : never : R export type OutputContext = unsafeForkEffect.OutputContext - export type Options = unsafeForkEffect.Options + export interface Options extends unsafeForkEffect.Options {} } export const forkEffect: {