diff --git a/packages/effect-fc/src/ErrorObserver.ts b/packages/effect-fc/src/ErrorObserver.ts index 921d6f9..cbb1cbe 100644 --- a/packages/effect-fc/src/ErrorObserver.ts +++ b/packages/effect-fc/src/ErrorObserver.ts @@ -1,24 +1,46 @@ -import { type Cause, Context, type Effect, Layer, type Queue, type Scope } from "effect" +import { type Cause, Context, Effect, Layer, Option, Pipeable, Predicate, PubSub, type Queue, type Scope } from "effect" -export interface ErrorObserver { - handle>(effect: Eff): Eff +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, ErrorObserver> => Context.GenericTag("@effect-fc/ErrorObserver/ErrorObserver") +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> -export namespace make { - export type Handler = ( - self: Effect.Effect>, - push: { - failure(failure: NoInfer): Effect.Effect - defect(defect: unknown): Effect.Effect - }, - ) => Effect.Effect + constructor( + private 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)) + } } -export const make = () => ( - f: make.Handler, EOut> -): Effect.Effect> => + +export const isErrorObserver = (u: unknown): u is ErrorObserver => Predicate.hasProperty(u, TypeId) + +export const layer: Layer.Layer = Layer.effect(ErrorObserver(), Effect.andThen( + PubSub.unbounded>(), + pubsub => 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, + }), +) diff --git a/packages/effect-fc/src/ReactRuntime.ts b/packages/effect-fc/src/ReactRuntime.ts index cdbe329..9fe4552 100644 --- a/packages/effect-fc/src/ReactRuntime.ts +++ b/packages/effect-fc/src/ReactRuntime.ts @@ -2,6 +2,7 @@ import { Effect, Layer, ManagedRuntime, Predicate, type Runtime } 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/ReactRuntime/ReactRuntime") @@ -16,16 +17,21 @@ export interface ReactRuntime { const ReactRuntimeProto = Object.freeze({ [TypeId]: TypeId } as const) +export const Prelude: Layer.Layer = Layer.mergeAll( + Component.ScopeMap.Default, + ErrorObserver.layer, +) + export const isReactRuntime = (u: unknown): u is ReactRuntime => Predicate.hasProperty(u, TypeId) export const make = ( layer: Layer.Layer, memoMap?: Layer.MemoMap, -): ReactRuntime => Object.setPrototypeOf( +): ReactRuntime | R, ER> => Object.setPrototypeOf( Object.assign(function() {}, { runtime: ManagedRuntime.make( - Layer.merge(layer, Component.ScopeMap.Default), + Layer.merge(layer, Prelude), memoMap, ), // biome-ignore lint/style/noNonNullAssertion: context initialization diff --git a/packages/effect-fc/src/index.ts b/packages/effect-fc/src/index.ts index 2fd9c2c..0b07c23 100644 --- a/packages/effect-fc/src/index.ts +++ b/packages/effect-fc/src/index.ts @@ -1,5 +1,6 @@ export * as Async from "./Async.js" export * as Component from "./Component.js" +export * as ErrorObserver from "./ErrorObserver.js" export * as Form from "./Form.js" export * as Memoized from "./Memoized.js" export * as PropertyPath from "./PropertyPath.js"