import { Context, Effect, ExecutionStrategy, Function, Pipeable, Runtime, Scope, String, Tracer, type Types, type Utils } from "effect" import * as React from "react" import * as Hook from "./Hook.js" export interface Component extends Pipeable.Pipeable { (props: P): Effect.Effect readonly displayName?: string readonly options: Options } export interface Options { readonly finalizerExecutionMode: "sync" | "fork" readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy } export type Error = T extends Component ? E : never export type Context = T extends Component ? R : never export type Props = T extends Component ? P : never export const nonReactiveTags = [Tracer.ParentSpan] as const export interface MakeOptions { readonly traced?: boolean readonly finalizerExecutionMode?: "sync" | "fork" readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy } export const make = < Eff extends Utils.YieldWrap>, P extends {} = {}, >( body: (props: P) => Generator, options?: MakeOptions, ): Component< [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never, P > => { const displayName = !String.isEmpty(body.name) ? body.name : undefined const f = ((options?.traced ?? true) ? displayName ? Effect.fn(displayName)(body) : Effect.fn(body) : Effect.fnUntraced(body) ) as Component f.pipe = function pipe() { return Pipeable.pipeArguments(this, arguments) }; (f as Types.Mutable).displayName = displayName; (f as Types.Mutable).options = { finalizerExecutionMode: options?.finalizerExecutionMode ?? "sync", finalizerExecutionStrategy: options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential, } return f } export const useFC: { ( self: Component ): Effect.Effect, never, Exclude> } = Effect.fn("useFC")(function* ( self: Component ) { const runtimeRef = React.useRef>>(null!) runtimeRef.current = yield* Effect.runtime>() return React.useCallback(function ScopeProvider(props: P) { const scope = Runtime.runSync(runtimeRef.current)(Hook.useScope( Array.from( Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values() ), self.options, )) const FC = React.useMemo(() => { const f = (props: P) => Runtime.runSync(runtimeRef.current)( Effect.provideService(self(props), Scope.Scope, scope) ) f.displayName = self.displayName ?? "Anonymous" return f }, [scope]) return React.createElement(FC, props) }, []) }) export const useSuspenseFC: { ( self: Component ): Effect.Effect, never, Exclude> } = Effect.fn("useSuspenseFC")(function* ( self: Component ) { const runtimeRef = React.useRef>>(null!) runtimeRef.current = yield* Effect.runtime>() return React.useMemo(() => function ScopeProvider(props: P) { const scope = Runtime.runSync(runtimeRef.current)(Hook.useScope([], self.options)) const FC = React.useMemo(() => { const inner = (props: { readonly promise: Promise }) => React.use(props.promise) const f = (props: P) => { const promise = Runtime.runPromise(runtimeRef.current)( Effect.provideService(self(props), Scope.Scope, scope) ) return React.createElement( React.Suspense, { fallback: "Loading..." }, React.createElement(inner, { promise }), ) } f.displayName = self.displayName ?? "Anonymous" return f }, [scope]) return React.createElement(FC, props) }, Array.from( Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values() )) }) export const use: { ( self: Component, fn: (Component: React.FC

) => React.ReactNode, ): Effect.Effect> } = Effect.fn("use")(function*(self, fn) { return fn(yield* useFC(self)) }) export const withRuntime: { ( context: React.Context>, ): (self: Component) => React.FC

( self: Component, context: React.Context>, ): React.FC

} = Function.dual(2, ( self: Component, context: React.Context>, ): React.FC

=> function WithRuntime(props) { const runtime = React.useContext(context) return React.createElement(Runtime.runSync(runtime)(useFC(self)), props) })