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") export type TypeId = typeof TypeId export interface ErrorObserver extends Pipeable.Pipeable { readonly [TypeId]: TypeId handle(effect: Effect.Effect): Effect.Effect readonly subscribe: Effect.Effect>, never, Scope.Scope> } export const ErrorObserver = (): Context.Tag> => Context.GenericTag("@effect-fc/ErrorObserver/ErrorObserver") class ErrorObserverImpl extends Pipeable.Class() implements ErrorObserver { readonly [TypeId]: TypeId = TypeId readonly subscribe: Effect.Effect>, never, Scope.Scope> constructor( readonly pubsub: PubSub.PubSub> ) { super() this.subscribe = pubsub.subscribe } handle(effect: Effect.Effect): Effect.Effect { return Effect.tapErrorCause(effect, cause => PubSub.publish(this.pubsub, cause as Cause.Cause)) } } 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.unwrapEffect(Effect.map( PubSub.unbounded>(), pubsub => Layer.merge( Supervisor.addSupervisor(new ErrorObserverSupervisorImpl(pubsub)), Layer.succeed(ErrorObserver(), new ErrorObserverImpl(pubsub)), ), )) export const handle = (effect: Effect.Effect): Effect.Effect => Effect.andThen( Effect.serviceOption(ErrorObserver()), Option.match({ onSome: observer => observer.handle(effect), onNone: () => effect, }), )