From 68bf55fa74d6f001e857d3f1a53d40b28bbf07d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Fri, 11 Jul 2025 04:42:21 +0200 Subject: [PATCH] Component refactoring --- packages/effect-fc/src/Component.ts | 45 +++++++++++++++-------------- packages/effect-fc/src/utils.ts | 3 ++ 2 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 packages/effect-fc/src/utils.ts diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index 398e9f3..d7ff100 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -1,10 +1,11 @@ -import { Context, Effect, ExecutionStrategy, Function, Pipeable, Runtime, Scope, String, Tracer, type Types, type Utils } from "effect" +import { Context, Effect, ExecutionStrategy, Function, Pipeable, Runtime, Scope, String, Tracer, type Utils } from "effect" import * as React from "react" import * as Hook from "./Hook.js" +import type { ExcludeKeys } from "./utils.js" export interface Component extends Pipeable.Pipeable { - (props: P): Effect.Effect + readonly body: (props: P) => Effect.Effect readonly displayName?: string readonly options: Options } @@ -18,7 +19,12 @@ export type Error = T extends Component ? E : ne export type Context = T extends Component ? R : never export type Props = T extends Component ? P : never -export const nonReactiveTags = [Tracer.ParentSpan] as const + +const ComponentProto = { + pipe() { return Pipeable.pipeArguments(this, arguments) } +} as const + +const nonReactiveTags = [Tracer.ParentSpan] as const export interface MakeOptions { @@ -40,21 +46,18 @@ export const make = < > => { 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 + return Object.setPrototypeOf({ + body: (options?.traced ?? true) + ? displayName + ? Effect.fn(displayName)(body) + : Effect.fn(body) + : Effect.fnUntraced(body), + displayName, + options: { + finalizerExecutionMode: options?.finalizerExecutionMode ?? "sync", + finalizerExecutionStrategy: options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential, + }, + }, ComponentProto) } @@ -78,7 +81,7 @@ export const useFC: { const FC = React.useMemo(() => { const f = (props: P) => Runtime.runSync(runtimeRef.current)( - Effect.provideService(self(props), Scope.Scope, scope) + Effect.provideService(self.body(props), Scope.Scope, scope) ) f.displayName = self.displayName ?? "Anonymous" return f @@ -89,7 +92,7 @@ export const useFC: { }) export const useSuspenseFC: { - ( + >( self: Component ): Effect.Effect< React.FC

, @@ -115,7 +118,7 @@ export const useSuspenseFC: { const f = ({ suspenseProps, ...props }: P & { readonly suspenseProps: React.SuspenseProps }) => { const promise = Runtime.runPromise(runtimeRef.current)( - Effect.provideService(self(props), Scope.Scope, scope) + Effect.provideService(self.body(props as P), Scope.Scope, scope) ) return React.createElement( diff --git a/packages/effect-fc/src/utils.ts b/packages/effect-fc/src/utils.ts new file mode 100644 index 0000000..44ed408 --- /dev/null +++ b/packages/effect-fc/src/utils.ts @@ -0,0 +1,3 @@ +export type ExcludeKeys = K extends keyof T ? ( + { [P in K]?: never } & Omit +) : T