}
export declare namespace Component {
export type Default = Component
>
export type Any = Component
+
export type Signature = (props: any) => React.ReactNode
export type DefaultSignature = (props: P) => A
- export type Props = T["~Props"]
- export type Success = T["~Success"]
- export type Error = T["~Error"]
- export type Context = T["~Context"]
- export type Function = T["~Function"]
+
+ export type Props = [T] extends [Component] ? P : never
+ export type Success = [T] extends [Component] ? A : never
+ export type Error = [T] extends [Component] ? E : never
+ export type Context = [T] extends [Component] ? R : never
+ export type Function = [T] extends [Component] ? F : never
+
export type AsComponent = Component, Success, Error, Context, Function>
}
+
+export interface ComponentImpl
+extends Component
, ComponentImplPrototype {}
+
+export interface ComponentImplPrototype {
+ readonly use: Effect.Effect>
+
+ asFunctionComponent(contextRef: React.Ref>>): F
+ setFunctionComponentName(f: F): void
+ transformFunctionComponent(f: F): F
+}
+
+export const ComponentImplPrototype: ComponentImplPrototype = Object.freeze({
+ get use() { return use(this) },
+
+ asFunctionComponent(
+ this: ComponentImpl
,
+ contextRef: React.RefObject>>,
+ ) {
+ return (props: P) => Effect.runSyncWith(contextRef.current)(
+ Effect.andThen(
+ useScope([], this),
+ scope => Effect.provideService(this.body(props), Scope.Scope, scope),
+ )
+ )
+ },
+
+ setFunctionComponentName(
+ this: ComponentImpl
,
+ f: React.FC
,
+ ) {
+ f.displayName = this.displayName ?? "Anonymous"
+ },
+
+ transformFunctionComponent: identity,
+} as const)
+
+const use = Effect.fnUntraced(function*
(
+ self: ComponentImpl
+) {
+ // biome-ignore lint/style/noNonNullAssertion: React ref initialization
+ const contextRef = React.useRef>>(null!)
+ contextRef.current = yield* Effect.context>()
+
+ return yield* React.useState(() => Effect.runSyncWith(contextRef.current)(Effect.cachedFunction(
+ (_services: readonly any[]) => Effect.sync(() => {
+ const f = self.asFunctionComponent(contextRef)
+ self.setFunctionComponentName(f)
+ return self.transformFunctionComponent(f)
+ }),
+ Equivalence.array(Equivalence.strictEqual()),
+ )))[0](Array.from(
+ Context.omit(...self.nonReactiveTags)(contextRef.current).mapUnsafe.values()
+ ))
+})
+
+
+export interface ComponentPrototype
+extends Pipeable.Pipeable {
+ readonly [ComponentTypeId]: ComponentTypeId
+ readonly use: Effect.Effect>
+}
+
+export const ComponentPrototype: ComponentPrototype = Object.freeze(
+ Object.defineProperties(
+ {
+ [ComponentTypeId]: ComponentTypeId,
+ ...Pipeable.Prototype,
+ },
+ Object.getOwnPropertyDescriptors(ComponentImplPrototype),
+ ) as ComponentPrototype
+)
+
+
export interface ComponentOptions {
+ /**
+ * Custom display name for the component in React DevTools and debugging utilities.
+ */
readonly displayName?: string
+
+ /**
+ * Context tags that should not trigger component remount when their values change.
+ *
+ * @default [Tracer.ParentSpan]
+ */
readonly nonReactiveTags: readonly Context.Key[]
- readonly finalizerExecutionStrategy: "sequential" | "parallel"
+
+ /**
+ * Specifies the execution strategy for finalizers when the component unmounts or its scope closes.
+ * Determines whether finalizers execute sequentially or in parallel.
+ *
+ * @default ExecutionStrategy.sequential
+ */
+ readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy
+
+ /**
+ * Debounce duration before executing finalizers after component unmount.
+ * Prevents unnecessary cleanup work during rapid remount/unmount cycles,
+ * which is common in development and certain UI patterns.
+ *
+ * @default "100 millis"
+ */
readonly finalizerExecutionDebounce: Duration.Input
}
export const defaultOptions: ComponentOptions = {
nonReactiveTags: [Tracer.ParentSpan],
- finalizerExecutionStrategy: "sequential",
+ finalizerExecutionStrategy: ExecutionStrategy.sequential,
finalizerExecutionDebounce: "100 millis",
}
-export interface ComponentPrototype extends Pipeable.Pipeable {
- readonly [ComponentTypeId]: ComponentTypeId
- readonly use: Effect.Effect>
-}
-type ComponentImpl = Component
+export const isComponent = (u: unknown): u is Component.Default<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, ComponentTypeId)
-const makeFunctionComponent = (
- self: ComponentImpl,
- contextRef: React.RefObject>,
-): Component.Signature => {
- if ("asFunctionComponent" in self && typeof self.asFunctionComponent === "function") {
- return self.asFunctionComponent(contextRef)
+export declare namespace make {
+ export type Gen = {
+ >, A extends React.ReactNode, P extends {} = {}>(
+ body: (props: P) => Generator
+ ): Component.Default<
+ P, A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >
+ >, A, B extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ >, A, B, C extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ b: (_: B, props: NoInfer
) => C,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ >, A, B, C, D extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ >, A, B, C, D, E extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ >, A, B, C, D, E, F extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => F,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ >, A, B, C, D, E, F, G extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => F,
+ f: (_: F, props: NoInfer
) => G,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ >, A, B, C, D, E, F, G, H extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => F,
+ f: (_: F, props: NoInfer
) => G,
+ g: (_: G, props: NoInfer
) => H,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ >, A, B, C, D, E, F, G, H, I extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => F,
+ f: (_: F, props: NoInfer
) => G,
+ g: (_: G, props: NoInfer
) => H,
+ h: (_: H, props: NoInfer
) => I,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ >, A, B, C, D, E, F, G, H, I, J extends Effect.Effect, P extends {} = {}>(
+ body: (props: P) => Generator,
+ a: (
+ _: Effect.Effect<
+ A,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? E : never,
+ [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap>] ? R : never
+ >,
+ props: NoInfer,
+ ) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => F,
+ f: (_: F, props: NoInfer
) => G,
+ g: (_: G, props: NoInfer
) => H,
+ h: (_: H, props: NoInfer
) => I,
+ i: (_: I, props: NoInfer
) => J,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
}
- const FunctionComponent = (props: {}) => Effect.runSyncWith(contextRef.current)(
- Effect.flatMap(
- useScope([], self),
- scope => Effect.provideService(self.body(props), Scope.Scope, scope),
- ),
- )
- FunctionComponent.displayName = self.displayName ?? "Anonymous"
- return "transformFunctionComponent" in self && typeof self.transformFunctionComponent === "function"
- ? self.transformFunctionComponent(FunctionComponent)
- : FunctionComponent
-}
-const use = Effect.fnUntraced(function* (self: ComponentImpl) {
- const context = yield* Effect.context()
- const cached = componentCache.get(self)
- if (cached !== undefined) {
- cached.contextRef.current = context
- return cached.component
+ export type NonGen = {
+ , P extends {} = {}>(
+ body: (props: P) => Eff
+ ): Component.Default>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer) => Eff,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, B, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer) => B,
+ b: (_: B, props: NoInfer
) => Eff,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, B, C, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => Eff,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, B, C, D, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => Eff,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, B, C, D, E, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => Eff,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, B, C, D, E, F, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => F,
+ f: (_: F, props: NoInfer
) => Eff,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, B, C, D, E, F, G, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => F,
+ f: (_: F, props: NoInfer
) => G,
+ g: (_: G, props: NoInfer
) => Eff,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, B, C, D, E, F, G, H, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer) => B,
+ b: (_: B, props: NoInfer
) => C,
+ c: (_: C, props: NoInfer
) => D,
+ d: (_: D, props: NoInfer
) => E,
+ e: (_: E, props: NoInfer
) => F,
+ f: (_: F, props: NoInfer
) => G,
+ g: (_: G, props: NoInfer
) => H,
+ h: (_: H, props: NoInfer
) => Eff,
+ ): Component.Default
>, Effect.Effect.Error, Effect.Effect.Context>
+ , A, B, C, D, E, F, G, H, I, P extends {} = {}>(
+ body: (props: P) => A,
+ a: (_: A, props: NoInfer