Compare commits
6 Commits
91c98f30d6
...
7cf8e30408
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cf8e30408 | |||
|
|
6d52c4ee31 | ||
|
|
c80b25b0e0 | ||
|
|
7e14f27df4 | ||
|
|
5bc1e65a61 | ||
|
|
53bc4407b3 |
@@ -2,6 +2,7 @@
|
|||||||
import { Effect, Function, Predicate, Runtime, Scope } from "effect"
|
import { Effect, Function, Predicate, Runtime, Scope } from "effect"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as Component from "./Component.js"
|
import * as Component from "./Component.js"
|
||||||
|
import * as ErrorObserver from "./ErrorObserver.js"
|
||||||
|
|
||||||
|
|
||||||
export const TypeId: unique symbol = Symbol.for("@effect-fc/Async/Async")
|
export const TypeId: unique symbol = Symbol.for("@effect-fc/Async/Async")
|
||||||
@@ -31,10 +32,10 @@ const SuspenseProto = Object.freeze({
|
|||||||
|
|
||||||
return ({ fallback, name, ...props }: Async.Props) => {
|
return ({ fallback, name, ...props }: Async.Props) => {
|
||||||
const promise = Runtime.runPromise(runtimeRef.current)(
|
const promise = Runtime.runPromise(runtimeRef.current)(
|
||||||
Effect.andThen(
|
ErrorObserver.handle(Effect.andThen(
|
||||||
Component.useScope([], this),
|
Component.useScope([], this),
|
||||||
scope => Effect.provideService(this.body(props as P), Scope.Scope, scope),
|
scope => Effect.provideService(this.body(props as P), Scope.Scope, scope),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
/** 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 { 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 React from "react"
|
||||||
|
import * as ErrorObserver from "./ErrorObserver.js"
|
||||||
import { Memoized } from "./index.js"
|
import { Memoized } from "./index.js"
|
||||||
|
|
||||||
|
|
||||||
@@ -75,10 +76,10 @@ const ComponentProto = Object.freeze({
|
|||||||
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
|
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
|
||||||
) {
|
) {
|
||||||
return (props: P) => Runtime.runSync(runtimeRef.current)(
|
return (props: P) => Runtime.runSync(runtimeRef.current)(
|
||||||
Effect.andThen(
|
ErrorObserver.handle(Effect.andThen(
|
||||||
useScope([], this),
|
useScope([], this),
|
||||||
scope => Effect.provideService(this.body(props), Scope.Scope, scope),
|
scope => Effect.provideService(this.body(props), Scope.Scope, scope),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
} as const)
|
} as const)
|
||||||
@@ -547,14 +548,15 @@ const runReactEffect = <E, R>(
|
|||||||
() => {
|
() => {
|
||||||
switch (options?.finalizerExecutionMode ?? "fork") {
|
switch (options?.finalizerExecutionMode ?? "fork") {
|
||||||
case "sync":
|
case "sync":
|
||||||
Runtime.runSync(runtime)(Scope.close(scope, Exit.void))
|
Runtime.runSync(runtime)(ErrorObserver.handle(Scope.close(scope, Exit.void)))
|
||||||
break
|
break
|
||||||
case "fork":
|
case "fork":
|
||||||
Runtime.runFork(runtime)(Scope.close(scope, Exit.void))
|
Runtime.runFork(runtime)(ErrorObserver.handle(Scope.close(scope, Exit.void)))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
ErrorObserver.handle,
|
||||||
Runtime.runSync(runtime),
|
Runtime.runSync(runtime),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
46
packages/effect-fc/src/ErrorObserver.ts
Normal file
46
packages/effect-fc/src/ErrorObserver.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { type Cause, Context, Effect, Layer, Option, Pipeable, Predicate, PubSub, type Queue, type Scope } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
export const TypeId: unique symbol = Symbol.for("@effect-fc/ErrorObserver/ErrorObserver")
|
||||||
|
export type TypeId = typeof TypeId
|
||||||
|
|
||||||
|
export interface ErrorObserver<in out E = never> extends Pipeable.Pipeable {
|
||||||
|
readonly [TypeId]: TypeId
|
||||||
|
handle<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
|
||||||
|
readonly subscribe: Effect.Effect<Queue.Dequeue<Cause.Cause<E>>, never, Scope.Scope>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ErrorObserver = <E = never>(): Context.Tag<ErrorObserver, ErrorObserver<E>> => Context.GenericTag("@effect-fc/ErrorObserver/ErrorObserver")
|
||||||
|
|
||||||
|
class ErrorObserverImpl<in out E = never>
|
||||||
|
extends Pipeable.Class() implements ErrorObserver<E> {
|
||||||
|
readonly [TypeId]: TypeId = TypeId
|
||||||
|
readonly subscribe: Effect.Effect<Queue.Dequeue<Cause.Cause<E>>, never, Scope.Scope>
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly pubsub: PubSub.PubSub<Cause.Cause<E>>
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.subscribe = pubsub.subscribe
|
||||||
|
}
|
||||||
|
|
||||||
|
handle<A, EffE, R>(effect: Effect.Effect<A, EffE, R>): Effect.Effect<A, EffE, R> {
|
||||||
|
return Effect.tapErrorCause(effect, cause => PubSub.publish(this.pubsub, cause as Cause.Cause<E>))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const isErrorObserver = (u: unknown): u is ErrorObserver<unknown> => Predicate.hasProperty(u, TypeId)
|
||||||
|
|
||||||
|
export const layer: Layer.Layer<ErrorObserver> = Layer.effect(ErrorObserver(), Effect.andThen(
|
||||||
|
PubSub.unbounded<Cause.Cause<never>>(),
|
||||||
|
pubsub => new ErrorObserverImpl(pubsub),
|
||||||
|
))
|
||||||
|
|
||||||
|
export const handle = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => Effect.andThen(
|
||||||
|
Effect.serviceOption(ErrorObserver()),
|
||||||
|
Option.match({
|
||||||
|
onSome: observer => observer.handle(effect),
|
||||||
|
onNone: () => effect,
|
||||||
|
}),
|
||||||
|
)
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
||||||
import { Effect, Layer, ManagedRuntime, Predicate, type Runtime } from "effect"
|
import { Effect, Layer, ManagedRuntime, Predicate, Runtime, Scope } from "effect"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as Component from "./Component.js"
|
import * as Component from "./Component.js"
|
||||||
|
import * as ErrorObserver from "./ErrorObserver.js"
|
||||||
|
|
||||||
|
|
||||||
export const TypeId: unique symbol = Symbol.for("@effect-fc/ReactRuntime/ReactRuntime")
|
export const TypeId: unique symbol = Symbol.for("@effect-fc/ReactRuntime/ReactRuntime")
|
||||||
@@ -16,16 +17,21 @@ export interface ReactRuntime<R, ER> {
|
|||||||
|
|
||||||
const ReactRuntimeProto = Object.freeze({ [TypeId]: TypeId } as const)
|
const ReactRuntimeProto = Object.freeze({ [TypeId]: TypeId } as const)
|
||||||
|
|
||||||
|
export const Prelude: Layer.Layer<Component.ScopeMap | ErrorObserver.ErrorObserver> = Layer.mergeAll(
|
||||||
|
Component.ScopeMap.Default,
|
||||||
|
ErrorObserver.layer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
export const isReactRuntime = (u: unknown): u is ReactRuntime<unknown, unknown> => Predicate.hasProperty(u, TypeId)
|
export const isReactRuntime = (u: unknown): u is ReactRuntime<unknown, unknown> => Predicate.hasProperty(u, TypeId)
|
||||||
|
|
||||||
export const make = <R, ER>(
|
export const make = <R, ER>(
|
||||||
layer: Layer.Layer<R, ER>,
|
layer: Layer.Layer<R, ER>,
|
||||||
memoMap?: Layer.MemoMap,
|
memoMap?: Layer.MemoMap,
|
||||||
): ReactRuntime<R | Component.ScopeMap, ER> => Object.setPrototypeOf(
|
): ReactRuntime<Layer.Layer.Success<typeof Prelude> | R, ER> => Object.setPrototypeOf(
|
||||||
Object.assign(function() {}, {
|
Object.assign(function() {}, {
|
||||||
runtime: ManagedRuntime.make(
|
runtime: ManagedRuntime.make(
|
||||||
Layer.merge(layer, Component.ScopeMap.Default),
|
Layer.merge(layer, Prelude),
|
||||||
memoMap,
|
memoMap,
|
||||||
),
|
),
|
||||||
// biome-ignore lint/style/noNonNullAssertion: context initialization
|
// biome-ignore lint/style/noNonNullAssertion: context initialization
|
||||||
@@ -54,16 +60,20 @@ export const Provider = <R, ER>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProviderInnerProps<R, ER> {
|
|
||||||
readonly runtime: ReactRuntime<R, ER>
|
|
||||||
readonly promise: Promise<Runtime.Runtime<R>>
|
|
||||||
readonly children?: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProviderInner = <R, ER>(
|
const ProviderInner = <R, ER>(
|
||||||
{ runtime, promise, children }: ProviderInnerProps<R, ER>
|
{ runtime, promise, children }: {
|
||||||
): React.ReactNode => React.createElement(
|
readonly runtime: ReactRuntime<R, ER>
|
||||||
runtime.context,
|
readonly promise: Promise<Runtime.Runtime<R>>
|
||||||
{ value: React.use(promise) },
|
readonly children?: React.ReactNode
|
||||||
children,
|
}
|
||||||
)
|
): React.ReactNode => {
|
||||||
|
const effectRuntime = React.use(promise)
|
||||||
|
const scope = Runtime.runSync(effectRuntime)(Component.useScope([effectRuntime]))
|
||||||
|
Runtime.runSync(effectRuntime)(Effect.provideService(
|
||||||
|
Component.useOnChange(() => Effect.addFinalizer(() => runtime.runtime.disposeEffect), [scope]),
|
||||||
|
Scope.Scope,
|
||||||
|
scope,
|
||||||
|
))
|
||||||
|
|
||||||
|
return React.createElement(runtime.context, { value: effectRuntime }, children)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export * as Async from "./Async.js"
|
export * as Async from "./Async.js"
|
||||||
export * as Component from "./Component.js"
|
export * as Component from "./Component.js"
|
||||||
|
export * as ErrorObserver from "./ErrorObserver.js"
|
||||||
export * as Form from "./Form.js"
|
export * as Form from "./Form.js"
|
||||||
export * as Memoized from "./Memoized.js"
|
export * as Memoized from "./Memoized.js"
|
||||||
export * as PropertyPath from "./PropertyPath.js"
|
export * as PropertyPath from "./PropertyPath.js"
|
||||||
|
|||||||
Reference in New Issue
Block a user