import { Context, Effect, Effectable, ExecutionStrategy, Function, identity, Predicate, Runtime, Scope, String, Tracer, type Types } from "effect" import * as React from "react" import { Hooks } from "./hooks/index.js" import * as Memoized from "./Memoized.js" export const TypeId: unique symbol = Symbol.for("effect-fc/Component") export type TypeId = typeof TypeId export interface Component extends Effect.Effect>, Component.Options { new(_: never): {} readonly [TypeId]: TypeId readonly ["~FunctionComponent"]: F readonly ["~Props"]: FunctionComponent.Props readonly ["~Success"]: FunctionComponent.Success readonly ["~Error"]: E readonly ["~Context"]: R /** @internal */ readonly body: (props: FunctionComponent.Props) => Effect.Effect, E, R> /** @internal */ makeFunctionComponent( runtimeRef: React.Ref>>, scope: Scope.Scope, ): F } export namespace Component { export type FunctionComponent> = T extends Component ? F : never export type Error> = T extends Component ? E : never export type Context> = T extends Component ? R : never export type AsComponent> = Component, Error, Context> export interface Options { readonly displayName?: string readonly finalizerExecutionMode: "sync" | "fork" readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy } } export type FunctionComponent = (...args: readonly any[]) => React.ReactNode export namespace FunctionComponent { export type Props = T extends (...args: readonly [infer P, ...any[]]) => infer _A ? P : never export type Success = T extends (...args: infer _Args) => infer A ? A : never } const ComponentProto = Object.freeze({ ...Effectable.CommitPrototype, [TypeId]: TypeId, commit: Effect.fnUntraced(function* ( this: Component ) { const self = this const runtimeRef = React.useRef>>(null!) runtimeRef.current = yield* Effect.runtime>() return React.useCallback(function ScopeProvider(props: FunctionComponent.Props) { const scope = Runtime.runSync(runtimeRef.current)(Hooks.useScope( Array.from( Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values() ), self, )) const FC = React.useMemo(() => { const f: React.FC> = self.makeFunctionComponent(runtimeRef, scope) f.displayName = self.displayName ?? "Anonymous" return Memoized.isMemoized(self) ? React.memo(f, self.propsAreEqual) : f }, [scope]) return React.createElement(FC, props) }, []) }), makeFunctionComponent( this: Component, runtimeRef: React.RefObject>>, scope: Scope.Scope, ) { return (props: FunctionComponent.Props) => Runtime.runSync(runtimeRef.current)( Effect.provideService(this.body(props), Scope.Scope, scope) ) }, } as const) const defaultOptions = { finalizerExecutionMode: "sync", finalizerExecutionStrategy: ExecutionStrategy.sequential, } as const const nonReactiveTags = [Tracer.ParentSpan] as const export const isComponent = (u: unknown): u is Component => Predicate.hasProperty(u, TypeId) export const make = ( body: (...args: Args) => Effect.Effect ): Component<(...args: Args) => A, E, R> => Object.setPrototypeOf( Object.assign(function() {}, defaultOptions, { body, displayName: !String.isEmpty(body.name) ? body.name : undefined, }), ComponentProto, ) export const withFunctionComponentSignature: { (): >(self: T) => & Omit> & Component, Component.Context> } = () => identity export const withOptions: { >( options: Partial ): (self: T) => T >( self: T, options: Partial, ): T } = Function.dual(2, >( self: T, options: Partial, ): T => Object.setPrototypeOf( Object.assign(function() {}, self, options), Object.getPrototypeOf(self), )) export const withRuntime: { ( context: React.Context>, ): (self: Component>) => F ( self: Component>, context: React.Context>, ): F } = Function.dual(2, ( self: Component, context: React.Context>, ) => function WithRuntime(props: FunctionComponent.Props) { return React.createElement( Runtime.runSync(React.useContext(context))(self), props, ) })