diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index 0ae4a90..548182f 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -4,12 +4,15 @@ import * as Hook from "./Hook.js" import type { ExcludeKeys } from "./utils.js" -export interface Component extends Pipeable.Pipeable { +export const TypeId: unique symbol = Symbol.for("effect-fc/Component") +export type TypeId = typeof TypeId + +export interface Component extends Pipeable.Pipeable, Component.Options { new(_: never): {} + readonly [TypeId]: TypeId readonly body: (props: P) => Effect.Effect readonly displayName?: string - readonly options: Component.Options } export namespace Component { @@ -25,17 +28,22 @@ export namespace Component { const ComponentProto = Object.seal({ + [TypeId]: TypeId, pipe() { return Pipeable.pipeArguments(this, arguments) } } as const) -const defaultOptions: Component.Options = { - finalizerExecutionMode: "sync", - finalizerExecutionStrategy: ExecutionStrategy.sequential, -} +const makeWithDefaults = (): Component => Object.assign( + Object.setPrototypeOf(function() {}, ComponentProto), { + finalizerExecutionMode: "sync", + finalizerExecutionStrategy: ExecutionStrategy.sequential, + } +) const nonReactiveTags = [Tracer.ParentSpan] as const +export const isComponent = (u: unknown): u is Component => Predicate.hasProperty(u, TypeId) + export namespace make { export type Gen = { >, P extends {} = {}>( @@ -268,31 +276,31 @@ export const make: ( spanName: string, spanOptions?: Tracer.SpanOptions, ) => make.Gen & make.NonGen) -) = (spanNameOrBody: Function | string, ...pipeables: any[]) => { +) = (spanNameOrBody: Function | string, ...pipeables: any[]): any => { if (typeof spanNameOrBody !== "string") { const displayName = displayNameFromBody(spanNameOrBody) - return Object.setPrototypeOf({ + return Object.assign(makeWithDefaults(), { body: displayName ? Effect.fn(displayName)(spanNameOrBody as any, ...pipeables as []) : Effect.fn(spanNameOrBody as any, ...pipeables), displayName, - options: { ...defaultOptions }, - }, ComponentProto) + }) } else { const spanOptions = pipeables[0] - return (body: any, ...pipeables: any[]) => Object.setPrototypeOf({ + return (body: any, ...pipeables: any[]) => Object.assign(makeWithDefaults(), { body: Effect.fn(spanNameOrBody, spanOptions)(body, ...pipeables as []), displayName: displayNameFromBody(body) ?? spanNameOrBody, - options: { ...defaultOptions }, - }, ComponentProto) + }) } } -export const makeUntraced: make.Gen & make.NonGen = (body: Function, ...pipeables: any[]) => Object.setPrototypeOf({ +export const makeUntraced: make.Gen & make.NonGen = ( + body: Function, + ...pipeables: any[] +) => Object.assign(makeWithDefaults(), { body: Effect.fnUntraced(body as any, ...pipeables as []), displayName: displayNameFromBody(body), - options: { ...defaultOptions }, }, ComponentProto) const displayNameFromBody = (body: Function) => !String.isEmpty(body.name) ? body.name : undefined @@ -326,7 +334,7 @@ export const withOptions: { self: T, options: Partial, ): T => Object.setPrototypeOf( - { ...self, options: { ...self.options, ...options } }, + { ...self, ...options }, Object.getPrototypeOf(self), )) @@ -427,7 +435,7 @@ export const useFC: { Array.from( Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values() ), - self.options, + self, )) const FC = React.useMemo(() => {