Reffuse work
Some checks failed
Lint / lint (push) Failing after 9s

This commit is contained in:
Julien Valverdé
2025-01-12 23:17:28 +01:00
parent cd2df017ec
commit ed85f9804c
3 changed files with 72 additions and 24 deletions

View File

@@ -1,5 +1,6 @@
import { Context, Effect, Fiber, FiberRefs, Layer, ManagedRuntime, Ref, Runtime, RuntimeFlags, Scope, Stream, SubscriptionRef } from "effect" import { Context, Effect, Fiber, Layer, ManagedRuntime, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect"
import React from "react" import React from "react"
import * as ReffuseReactContext from "./ReffuseReactContext.js"
export interface ProviderProps<R, E> { export interface ProviderProps<R, E> {
@@ -10,31 +11,71 @@ export interface ProviderProps<R, E> {
export class Reffuse<R> { export class Reffuse<R> {
readonly Context = React.createContext<Runtime.Runtime<R>>(null!) readonly Context = React.createContext<ReffuseReactContext.ReffuseReactContext<R>>(null!)
readonly Provider: React.FC<ProviderProps<R, any>> readonly Provider: React.FC<ProviderProps<R, unknown>>
constructor( constructor(
runtime: Runtime.Runtime<R> runtime: Runtime.Runtime<R>
) { ) {
this.Provider = (props: ProviderProps<R, any>) => { this.Provider = (props: ProviderProps<R, unknown>) => {
const runtime = React.useMemo(() => Runtime.make({ const value = React.useMemo(() => ({
context: Context.empty(), runtime,
runtimeFlags: RuntimeFlags.make(), context: Effect.context<R>().pipe(
fiberRefs: FiberRefs.empty(), Effect.provide(props.layer),
}), []) Runtime.runSync(runtime),
),
}), [props.layer])
return ( return (
<this.Context <this.Context
{...props} {...props}
value={runtime} value={value}
/> />
) )
} }
} }
useRuntime(): Runtime.Runtime<R> { useRuntime(): Runtime.Runtime<never> {
return React.useContext(this.Context) return React.useContext(this.Context).runtime
}
useContext(): Context.Context<R> {
return React.useContext(this.Context).context
}
useRunSync() {
const { runtime, context } = React.useContext(this.Context)
return React.useCallback(<A, E>(effect: Effect.Effect<A, E, R>): A => effect.pipe(
Effect.provide(context),
Runtime.runSync(runtime),
), [runtime, context])
}
useRunPromise() {
const { runtime, context } = React.useContext(this.Context)
return React.useCallback(<A, E>(
effect: Effect.Effect<A, E, R>,
options?: { readonly signal?: AbortSignal },
): Promise<A> => effect.pipe(
Effect.provide(context),
effect => Runtime.runPromise(runtime)(effect, options),
), [runtime, context])
}
useRunFork() {
const { runtime, context } = React.useContext(this.Context)
return React.useCallback(<A, E>(
effect: Effect.Effect<A, E, R>,
options?: Runtime.RunForkOptions,
): Fiber.RuntimeFiber<A, E> => effect.pipe(
Effect.provide(context),
effect => Runtime.runFork(runtime)(effect, options),
), [runtime, context])
} }
@@ -43,32 +84,32 @@ export class Reffuse<R> {
deps?: React.DependencyList, deps?: React.DependencyList,
options?: Runtime.RunForkOptions, options?: Runtime.RunForkOptions,
): void { ): void {
const runtime = this.useRuntime() const runFork = this.useRunFork()
return React.useEffect(() => { return React.useEffect(() => {
const fiber = runtime.runFork(Effect.scoped(self), options) const fiber = runFork(Effect.scoped(self), options)
return () => { runtime.runFork(Fiber.interrupt(fiber)) } return () => { runFork(Fiber.interrupt(fiber)) }
}, [runtime, ...deps ?? []]) }, [runFork, ...deps ?? []])
} }
useRef<A>(value: A): SubscriptionRef.SubscriptionRef<A> { useRef<A>(value: A): SubscriptionRef.SubscriptionRef<A> {
const runtime = this.useRuntime() const runSync = this.useRunSync()
return React.useMemo(() => runtime.runSync(SubscriptionRef.make(value)), []) return React.useMemo(() => runSync(SubscriptionRef.make(value)), [])
} }
useRefFromEffect<A, E>(effect: Effect.Effect<A, E, R>): SubscriptionRef.SubscriptionRef<A> { useRefFromEffect<A, E>(effect: Effect.Effect<A, E, R>): SubscriptionRef.SubscriptionRef<A> {
const runtime = this.useRuntime() const runSync = this.useRunSync()
return React.useMemo(() => runtime.runSync(effect.pipe( return React.useMemo(() => runSync(effect.pipe(
Effect.flatMap(SubscriptionRef.make) Effect.flatMap(SubscriptionRef.make)
)), []) )), [])
} }
useRefState<A>(ref: SubscriptionRef.SubscriptionRef<A>): [A, React.Dispatch<React.SetStateAction<A>>] { useRefState<A>(ref: SubscriptionRef.SubscriptionRef<A>): [A, React.Dispatch<React.SetStateAction<A>>] {
const runtime = this.useRuntime() const runSync = this.useRunSync()
const initialState = React.useMemo(() => runtime.runSync(ref), [ref]) const initialState = React.useMemo(() => runSync(ref), [ref])
const [reactStateValue, setReactStateValue] = React.useState(initialState) const [reactStateValue, setReactStateValue] = React.useState(initialState)
this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() => this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() =>
@@ -76,7 +117,7 @@ export class Reffuse<R> {
)), [ref]) )), [ref])
const setValue = React.useCallback((setStateAction: React.SetStateAction<A>) => const setValue = React.useCallback((setStateAction: React.SetStateAction<A>) =>
runtime.runSync(Ref.update(ref, previousState => runSync(Ref.update(ref, previousState =>
typeof setStateAction === "function" typeof setStateAction === "function"
? (setStateAction as (prevState: A) => A)(previousState) ? (setStateAction as (prevState: A) => A)(previousState)
: setStateAction : setStateAction
@@ -89,5 +130,5 @@ export class Reffuse<R> {
} }
export const make = <ROut, E>(layer: Layer.Layer<ROut, E, never>): Reffuse<ROut, E> => export const make = <R>(): Reffuse<R> =>
new Reffuse(ManagedRuntime.make(layer)) new Reffuse(ManagedRuntime.make(layer))

View File

@@ -0,0 +1,7 @@
import type { Context, Runtime } from "effect"
export interface ReffuseReactContext<R> {
readonly runtime: Runtime.Runtime<never>
readonly context: Context.Context<R>
}