From 0718ba48bd99a18b45992d2d428a0bade3e70481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 8 Nov 2025 01:35:58 +0100 Subject: [PATCH] Fix ErrorObserver --- packages/effect-fc/src/Async.ts | 5 ++--- packages/effect-fc/src/Component.ts | 10 ++++------ packages/effect-fc/src/ErrorObserver.ts | 21 ++++++++++++++++++--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/effect-fc/src/Async.ts b/packages/effect-fc/src/Async.ts index 0a63777..a0dba11 100644 --- a/packages/effect-fc/src/Async.ts +++ b/packages/effect-fc/src/Async.ts @@ -2,7 +2,6 @@ import { Effect, Function, Predicate, Runtime, Scope } from "effect" import * as React from "react" import * as Component from "./Component.js" -import * as ErrorObserver from "./ErrorObserver.js" export const TypeId: unique symbol = Symbol.for("@effect-fc/Async/Async") @@ -32,10 +31,10 @@ const SuspenseProto = Object.freeze({ return ({ fallback, name, ...props }: Async.Props) => { const promise = Runtime.runPromise(runtimeRef.current)( - ErrorObserver.handle(Effect.andThen( + Effect.andThen( Component.useScope([], this), scope => Effect.provideService(this.body(props as P), Scope.Scope, scope), - )) + ) ) return React.createElement( diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index 921ccff..38d8bd0 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -2,7 +2,6 @@ /** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */ import { Context, type Duration, Effect, Effectable, Equivalence, ExecutionStrategy, Exit, Fiber, Function, HashMap, Layer, ManagedRuntime, Option, Predicate, Ref, Runtime, Scope, Tracer, type Utils } from "effect" import * as React from "react" -import * as ErrorObserver from "./ErrorObserver.js" import { Memoized } from "./index.js" @@ -76,10 +75,10 @@ const ComponentProto = Object.freeze({ runtimeRef: React.RefObject>>, ) { return (props: P) => Runtime.runSync(runtimeRef.current)( - ErrorObserver.handle(Effect.andThen( + Effect.andThen( useScope([], this), scope => Effect.provideService(this.body(props), Scope.Scope, scope), - )) + ) ) }, } as const) @@ -548,15 +547,14 @@ const runReactEffect = ( () => { switch (options?.finalizerExecutionMode ?? "fork") { case "sync": - Runtime.runSync(runtime)(ErrorObserver.handle(Scope.close(scope, Exit.void))) + Runtime.runSync(runtime)(Scope.close(scope, Exit.void)) break case "fork": - Runtime.runFork(runtime)(ErrorObserver.handle(Scope.close(scope, Exit.void))) + Runtime.runFork(runtime)(Scope.close(scope, Exit.void)) break } } ), - ErrorObserver.handle, Runtime.runSync(runtime), ) diff --git a/packages/effect-fc/src/ErrorObserver.ts b/packages/effect-fc/src/ErrorObserver.ts index cbb1cbe..243242f 100644 --- a/packages/effect-fc/src/ErrorObserver.ts +++ b/packages/effect-fc/src/ErrorObserver.ts @@ -1,4 +1,4 @@ -import { type Cause, Context, Effect, Layer, Option, Pipeable, Predicate, PubSub, type Queue, type Scope } from "effect" +import { type Cause, Context, Effect, Exit, Layer, Option, Pipeable, Predicate, PubSub, type Queue, type Scope, Supervisor } from "effect" export const TypeId: unique symbol = Symbol.for("@effect-fc/ErrorObserver/ErrorObserver") @@ -29,12 +29,27 @@ extends Pipeable.Class() implements ErrorObserver { } } +class ErrorObserverSupervisorImpl extends Supervisor.AbstractSupervisor { + readonly value = Effect.void + constructor(readonly pubsub: PubSub.PubSub>) { + super() + } + + onEnd(_value: Exit.Exit): void { + if (Exit.isFailure(_value)) + Effect.runSync(PubSub.publish(this.pubsub, _value.cause as Cause.Cause)) + } +} + export const isErrorObserver = (u: unknown): u is ErrorObserver => Predicate.hasProperty(u, TypeId) -export const layer: Layer.Layer = Layer.effect(ErrorObserver(), Effect.andThen( +export const layer: Layer.Layer = Layer.unwrapEffect(Effect.map( PubSub.unbounded>(), - pubsub => new ErrorObserverImpl(pubsub), + pubsub => Layer.merge( + Supervisor.addSupervisor(new ErrorObserverSupervisorImpl(pubsub)), + Layer.succeed(ErrorObserver(), new ErrorObserverImpl(pubsub)), + ), )) export const handle = (effect: Effect.Effect): Effect.Effect => Effect.andThen(