0.1.3 (#4)
All checks were successful
Publish / publish (push) Successful in 14s
Lint / lint (push) Successful in 11s

Co-authored-by: Julien Valverdé <julien.valverde@mailo.com>
Reviewed-on: https://gitea:3000/Thilawyn/effect-fc/pulls/4
This commit was merged in pull request #4.
This commit is contained in:
Julien Valverdé
2025-08-23 03:07:28 +02:00
parent 16fa750b30
commit 831a808568
55 changed files with 1539 additions and 859 deletions

View File

@@ -1,26 +1,41 @@
import { Context, Effect, Effectable, ExecutionStrategy, Function, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect"
import { Context, Effect, Effectable, ExecutionStrategy, Function, Predicate, Runtime, Scope, String, Tracer, type Types, type Utils } from "effect"
import * as React from "react"
import * as Hook from "./Hook.js"
import * as Memoized from "./Memoized.js"
import { Hooks } from "./hooks/index.js"
import * as Memo from "./Memo.js"
export const TypeId: unique symbol = Symbol.for("effect-fc/Component")
export type TypeId = typeof TypeId
export interface Component<P extends {} = {}, E = never, R = never>
extends Effect.Effect<React.FC<P>, never, Exclude<R, Scope.Scope>>, Component.Options {
export interface Component<P extends {}, A extends React.ReactNode, E, R>
extends
Effect.Effect<(props: P) => A, never, Exclude<R, Scope.Scope>>,
Component.Options
{
new(_: never): {}
readonly [TypeId]: TypeId
readonly ["~Props"]: P
readonly ["~Success"]: A
readonly ["~Error"]: E
readonly ["~Context"]: R
/** @internal */
makeFunctionComponent(runtimeRef: React.Ref<Runtime.Runtime<Exclude<R, Scope.Scope>>>, scope: Scope.Scope): React.FC<P>
readonly body: (props: P) => Effect.Effect<A, E, R>
/** @internal */
readonly body: (props: P) => Effect.Effect<React.ReactNode, E, R>
makeFunctionComponent(
runtimeRef: React.Ref<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
scope: Scope.Scope,
): (props: P) => A
}
export namespace Component {
export type Props<T> = T extends Component<infer P, infer _E, infer _R> ? P : never
export type Error<T> = T extends Component<infer _P, infer E, infer _R> ? E : never
export type Context<T> = T extends Component<infer _P, infer _E, infer R> ? R : never
export type Props<T extends Component<any, any, any, any>> = [T] extends [Component<infer P, infer _A, infer _E, infer _R>] ? P : never
export type Success<T extends Component<any, any, any, any>> = [T] extends [Component<infer _P, infer A, infer _E, infer _R>] ? A : never
export type Error<T extends Component<any, any, any, any>> = [T] extends [Component<infer _P, infer _A, infer E, infer _R>] ? E : never
export type Context<T extends Component<any, any, any, any>> = [T] extends [Component<infer _P, infer _A, infer _E, infer R>] ? R : never
export type AsComponent<T extends Component<any, any, any, any>> = Component<Props<T>, Success<T>, Error<T>, Context<T>>
export interface Options {
readonly displayName?: string
@@ -34,13 +49,15 @@ const ComponentProto = Object.freeze({
...Effectable.CommitPrototype,
[TypeId]: TypeId,
commit: Effect.fn("Component")(function* <P extends {}, E, R>(this: Component<P, E, R>) {
commit: Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode, E, R>(
this: Component<P, A, E, R>
) {
const self = this
const runtimeRef = React.useRef<Runtime.Runtime<Exclude<R, Scope.Scope>>>(null!)
runtimeRef.current = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
return React.useCallback(function ScopeProvider(props: P) {
const scope = Runtime.runSync(runtimeRef.current)(Hook.useScope(
return React.useRef(function ScopeProvider(props: P) {
const scope = Runtime.runSync(runtimeRef.current)(Hooks.useScope(
Array.from(
Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values()
),
@@ -48,22 +65,22 @@ const ComponentProto = Object.freeze({
))
const FC = React.useMemo(() => {
const f = self.makeFunctionComponent(runtimeRef, scope)
const f: React.FC<P> = self.makeFunctionComponent(runtimeRef, scope)
f.displayName = self.displayName ?? "Anonymous"
return Memoized.isMemoized(self)
return Memo.isMemo(self)
? React.memo(f, self.propsAreEqual)
: f
}, [scope])
return React.createElement(FC, props)
}, [])
}).current
}),
makeFunctionComponent <P extends {}, E, R>(
this: Component<P, E, R>,
makeFunctionComponent<P extends {}, A extends React.ReactNode, E, R>(
this: Component<P, A, E, R>,
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
scope: Scope.Scope,
): React.FC<P> {
) {
return (props: P) => Runtime.runSync(runtimeRef.current)(
Effect.provideService(this.body(props), Scope.Scope, scope)
)
@@ -78,14 +95,14 @@ const defaultOptions = {
const nonReactiveTags = [Tracer.ParentSpan] as const
export const isComponent = (u: unknown): u is Component<{}, unknown, unknown> => Predicate.hasProperty(u, TypeId)
export const isComponent = (u: unknown): u is Component<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, TypeId)
export namespace make {
export type Gen = {
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, P extends {} = {}>(
body: (props: P) => Generator<Eff, React.ReactNode, never>,
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A extends React.ReactNode, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>
): Component<
P,
P, A,
[Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E : never,
[Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R : never
>
@@ -98,8 +115,8 @@ export namespace make {
[Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R : never
>,
props: NoInfer<P>,
) => B
): Component<P, Effect.Effect.Error<B>, Effect.Effect.Context<B>>
) => B,
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<B>>, Effect.Effect.Error<B>, Effect.Effect.Context<B>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>,
a: (
@@ -111,7 +128,7 @@ export namespace make {
props: NoInfer<P>,
) => B,
b: (_: B, props: NoInfer<P>) => C,
): Component<P, Effect.Effect.Error<C>, Effect.Effect.Context<C>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<C>>, Effect.Effect.Error<C>, Effect.Effect.Context<C>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>,
a: (
@@ -124,7 +141,7 @@ export namespace make {
) => B,
b: (_: B, props: NoInfer<P>) => C,
c: (_: C, props: NoInfer<P>) => D,
): Component<P, Effect.Effect.Error<D>, Effect.Effect.Context<D>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<D>>, Effect.Effect.Error<D>, Effect.Effect.Context<D>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>,
a: (
@@ -138,7 +155,7 @@ export namespace make {
b: (_: B, props: NoInfer<P>) => C,
c: (_: C, props: NoInfer<P>) => D,
d: (_: D, props: NoInfer<P>) => E,
): Component<P, Effect.Effect.Error<E>, Effect.Effect.Context<E>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<E>>, Effect.Effect.Error<E>, Effect.Effect.Context<E>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>,
a: (
@@ -153,7 +170,7 @@ export namespace make {
c: (_: C, props: NoInfer<P>) => D,
d: (_: D, props: NoInfer<P>) => E,
e: (_: E, props: NoInfer<P>) => F,
): Component<P, Effect.Effect.Error<F>, Effect.Effect.Context<F>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<F>>, Effect.Effect.Error<F>, Effect.Effect.Context<F>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>,
a: (
@@ -169,7 +186,7 @@ export namespace make {
d: (_: D, props: NoInfer<P>) => E,
e: (_: E, props: NoInfer<P>) => F,
f: (_: F, props: NoInfer<P>) => G,
): Component<P, Effect.Effect.Error<G>, Effect.Effect.Context<G>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<G>>, Effect.Effect.Error<G>, Effect.Effect.Context<G>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>,
a: (
@@ -186,7 +203,7 @@ export namespace make {
e: (_: E, props: NoInfer<P>) => F,
f: (_: F, props: NoInfer<P>) => G,
g: (_: G, props: NoInfer<P>) => H,
): Component<P, Effect.Effect.Error<H>, Effect.Effect.Context<H>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<H>>, Effect.Effect.Error<H>, Effect.Effect.Context<H>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H, I extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>,
a: (
@@ -204,7 +221,7 @@ export namespace make {
f: (_: F, props: NoInfer<P>) => G,
g: (_: G, props: NoInfer<P>) => H,
h: (_: H, props: NoInfer<P>) => I,
): Component<P, Effect.Effect.Error<I>, Effect.Effect.Context<I>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<I>>, Effect.Effect.Error<I>, Effect.Effect.Context<I>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H, I, J extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>,
a: (
@@ -223,35 +240,35 @@ export namespace make {
g: (_: G, props: NoInfer<P>) => H,
h: (_: H, props: NoInfer<P>) => I,
i: (_: I, props: NoInfer<P>) => J,
): Component<P, Effect.Effect.Error<J>, Effect.Effect.Context<J>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<J>>, Effect.Effect.Error<J>, Effect.Effect.Context<J>>
}
export type NonGen = {
<Eff extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Eff
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B,
b: (_: B, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B,
b: (_: B, props: NoInfer<P>) => C,
c: (_: C, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B,
b: (_: B, props: NoInfer<P>) => C,
c: (_: C, props: NoInfer<P>) => D,
d: (_: D, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B,
@@ -259,7 +276,7 @@ export namespace make {
c: (_: C, props: NoInfer<P>) => D,
d: (_: D, props: NoInfer<P>) => E,
e: (_: E, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B,
@@ -268,7 +285,7 @@ export namespace make {
d: (_: D, props: NoInfer<P>) => E,
e: (_: E, props: NoInfer<P>) => F,
f: (_: F, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B,
@@ -278,7 +295,7 @@ export namespace make {
e: (_: E, props: NoInfer<P>) => F,
f: (_: F, props: NoInfer<P>) => G,
g: (_: G, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, H, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B,
@@ -289,7 +306,7 @@ export namespace make {
f: (_: F, props: NoInfer<P>) => G,
g: (_: G, props: NoInfer<P>) => H,
h: (_: H, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, H, I, P extends {} = {}>(
body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B,
@@ -301,7 +318,7 @@ export namespace make {
g: (_: G, props: NoInfer<P>) => H,
h: (_: H, props: NoInfer<P>) => I,
i: (_: I, props: NoInfer<P>) => Eff,
): Component<P, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
}
}
@@ -351,14 +368,14 @@ export const makeUntraced: make.Gen & make.NonGen = (
const displayNameFromBody = (body: Function) => !String.isEmpty(body.name) ? body.name : undefined
export const withOptions: {
<T extends Component<any, any, any>>(
<T extends Component<any, any, any, any>>(
options: Partial<Component.Options>
): (self: T) => T
<T extends Component<any, any, any>>(
<T extends Component<any, any, any, any>>(
self: T,
options: Partial<Component.Options>,
): T
} = Function.dual(2, <T extends Component<any, any, any>>(
} = Function.dual(2, <T extends Component<any, any, any, any>>(
self: T,
options: Partial<Component.Options>,
): T => Object.setPrototypeOf(
@@ -367,17 +384,17 @@ export const withOptions: {
))
export const withRuntime: {
<P extends {}, E, R>(
<P extends {}, A extends React.ReactNode, E, R>(
context: React.Context<Runtime.Runtime<R>>,
): (self: Component<P, E, R>) => React.FC<P>
<P extends {}, E, R>(
self: Component<P, E, R>,
): (self: Component<P, A, E, Types.NoInfer<R>>) => (props: P) => A
<P extends {}, A extends React.ReactNode, E, R>(
self: Component<P, A, E, Types.NoInfer<R>>,
context: React.Context<Runtime.Runtime<R>>,
): React.FC<P>
} = Function.dual(2, <P extends {}, E, R>(
self: Component<P, E, R>,
): (props: P) => A
} = Function.dual(2, <P extends {}, A extends React.ReactNode, E, R>(
self: Component<P, A, E, R>,
context: React.Context<Runtime.Runtime<R>>,
): React.FC<P> => function WithRuntime(props) {
) => function WithRuntime(props: P) {
return React.createElement(
Runtime.runSync(React.useContext(context))(self),
props,