From 9b25ef4b57e7b882976bc8d6e4821b25e8d58cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 19 Jul 2026 03:15:55 +0200 Subject: [PATCH] Fix --- packages/effect-fc-next/src/PubSub.ts | 4 +- packages/effect-fc-next/src/QueryClient.ts | 4 +- packages/effect-fc-next/src/ReactRuntime.ts | 17 +- packages/effect-fc-next/src/Result.ts | 250 -------------------- packages/effect-fc-next/src/Stream.ts | 4 +- packages/effect-fc-next/src/index.ts | 1 - packages/effect-fc-next/src/utils.ts | 3 - 7 files changed, 10 insertions(+), 273 deletions(-) delete mode 100644 packages/effect-fc-next/src/Result.ts delete mode 100644 packages/effect-fc-next/src/utils.ts diff --git a/packages/effect-fc-next/src/PubSub.ts b/packages/effect-fc-next/src/PubSub.ts index 27401bc..17c44bc 100644 --- a/packages/effect-fc-next/src/PubSub.ts +++ b/packages/effect-fc-next/src/PubSub.ts @@ -3,6 +3,8 @@ import type * as React from "react" import * as Component from "./Component.js" +export * from "effect/PubSub" + export const useFromReactiveValues = Effect.fnUntraced(function* ( values: A ): Effect.fn.Return, never, Scope.Scope> { @@ -13,5 +15,3 @@ export const useFromReactiveValues = Effect.fnUntraced(function* Effect.forkScoped(client.run), ) +export const layer = (options?: service.Options) => Layer.effect(QueryClient, service(options)) + export const QueryClientCacheKeyTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientCacheKey") export type QueryClientCacheKeyTypeId = typeof QueryClientCacheKeyTypeId diff --git a/packages/effect-fc-next/src/ReactRuntime.ts b/packages/effect-fc-next/src/ReactRuntime.ts index 4413e3e..c883af7 100644 --- a/packages/effect-fc-next/src/ReactRuntime.ts +++ b/packages/effect-fc-next/src/ReactRuntime.ts @@ -2,8 +2,6 @@ import { type Context, Effect, Layer, ManagedRuntime, Predicate } from "effect" import * as React from "react" import * as Component from "./Component.js" -import * as ErrorObserver from "./ErrorObserver.js" -import * as QueryClient from "./QueryClient.js" export const ReactRuntimeTypeId: unique symbol = Symbol.for("@effect-fc/ReactRuntime/ReactRuntime") @@ -16,18 +14,9 @@ export interface ReactRuntime { readonly context: React.Context> } -const ReactRuntimeProto = Object.freeze({ [ReactRuntimeTypeId]: ReactRuntimeTypeId } as const) - -export const preludeLayer: Layer.Layer< - | Component.ScopeMap - | ErrorObserver.ErrorObserver - | QueryClient.QueryClient -> = Layer.mergeAll( - Component.ScopeMap.layer, - ErrorObserver.layer, - QueryClient.QueryClient.Default, -) +const ReactRuntimePrototype = Object.freeze({ [ReactRuntimeTypeId]: ReactRuntimeTypeId } as const) +export const preludeLayer: Layer.Layer = Layer.mergeAll(Component.ScopeMap.layer) export const isReactRuntime = (u: unknown): u is ReactRuntime => Predicate.hasProperty(u, ReactRuntimeTypeId) @@ -43,7 +32,7 @@ export const make = ( // biome-ignore lint/style/noNonNullAssertion: context initialization context: React.createContext>(null!), }), - ReactRuntimeProto, + ReactRuntimePrototype, ) diff --git a/packages/effect-fc-next/src/Result.ts b/packages/effect-fc-next/src/Result.ts deleted file mode 100644 index 1c92d99..0000000 --- a/packages/effect-fc-next/src/Result.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { Cause, Context, Data, Effect, Equal, Exit, type Fiber, Hash, Layer, Match, Pipeable, Predicate, pipe, type Scope, SubscriptionRef } from "effect" -import { Lens } from "effect-lens" -import type { View } from "effect-lens/View" - - -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, - ) - }, -} 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.NoSuchElementError()) - } -} - - -export interface Progress

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

() => Context.Service>("@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.Service | 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: View, never, never>, fiber: Fiber.Fiber], - never, - Scope.Scope | unsafeForkEffect.OutputContext -> { - const state = Lens.fromSubscriptionRef(yield* SubscriptionRef.make>( - options?.initial ?? initial(), - )) - - 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 => Lens.set(state, fromExit(exit))) - }).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: View, never, never>, fiber: Fiber.Fiber], - never, - Scope.Scope | forkEffect.OutputContext - > -} = unsafeForkEffect diff --git a/packages/effect-fc-next/src/Stream.ts b/packages/effect-fc-next/src/Stream.ts index 67bf82e..35af3f4 100644 --- a/packages/effect-fc-next/src/Stream.ts +++ b/packages/effect-fc-next/src/Stream.ts @@ -3,6 +3,8 @@ import * as React from "react" import * as Component from "./Component.js" +export * from "effect/Stream" + export const use: { ( stream: Stream.Stream @@ -29,5 +31,3 @@ export const use: { return reactStateValue as Option.Some }) - -export * from "effect/Stream" diff --git a/packages/effect-fc-next/src/index.ts b/packages/effect-fc-next/src/index.ts index b114c1c..33039ed 100644 --- a/packages/effect-fc-next/src/index.ts +++ b/packages/effect-fc-next/src/index.ts @@ -11,7 +11,6 @@ export * as PubSub from "./PubSub.js" export * as Query from "./Query.js" export * as QueryClient from "./QueryClient.js" export * as ReactRuntime from "./ReactRuntime.js" -export * as Result from "./Result.js" export * as SetStateAction from "./SetStateAction.js" export * as Stream from "./Stream.js" export * as View from "./View.js" diff --git a/packages/effect-fc-next/src/utils.ts b/packages/effect-fc-next/src/utils.ts deleted file mode 100644 index 44ed408..0000000 --- a/packages/effect-fc-next/src/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type ExcludeKeys = K extends keyof T ? ( - { [P in K]?: never } & Omit -) : T